diff --git a/package-lock.json b/package-lock.json index b0ff470..615fd3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6134,6 +6134,11 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" }, + "hammerjs": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", + "integrity": "sha1-BO93hiz/K7edMPdpIJWTAiK/YPE=" + }, "handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", diff --git a/package.json b/package.json index 5be04e3..ee458c5 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@ionic/angular": "^5.0.0", "cordova-res": "^0.15.2", "faker": "^5.1.0", + "hammerjs": "^2.0.8", "moment": "^2.29.1", "rxjs": "~6.5.5", "sharp": "^0.27.0", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 610553e..a23a87e 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,5 +1,5 @@ import { NgModule } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; +import { BrowserModule, HAMMER_GESTURE_CONFIG, HammerModule } from '@angular/platform-browser'; import { RouteReuseStrategy } from '@angular/router'; import { IonicModule, IonicRouteStrategy } from '@ionic/angular'; @@ -10,15 +10,21 @@ import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { ServiceWorkerModule } from '@angular/service-worker'; import { environment } from '../environments/environment'; +import { MyHammerConfig } from './hammer-config'; @NgModule({ declarations: [AppComponent], entryComponents: [], - imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule, ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production })], + imports: [BrowserModule, + IonicModule.forRoot(), + AppRoutingModule, + HammerModule, + ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production })], providers: [ StatusBar, SplashScreen, - { provide: RouteReuseStrategy, useClass: IonicRouteStrategy } + { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }, + { provide: HAMMER_GESTURE_CONFIG, useClass: MyHammerConfig }, ], bootstrap: [AppComponent] }) diff --git a/src/app/hammer-config.spec.ts b/src/app/hammer-config.spec.ts new file mode 100644 index 0000000..ddd19ac --- /dev/null +++ b/src/app/hammer-config.spec.ts @@ -0,0 +1,7 @@ +import { HammerConfig } from './hammer-config'; + +describe('HammerConfig', () => { + it('should create an instance', () => { + expect(new HammerConfig()).toBeTruthy(); + }); +}); diff --git a/src/app/hammer-config.ts b/src/app/hammer-config.ts new file mode 100644 index 0000000..08e6c38 --- /dev/null +++ b/src/app/hammer-config.ts @@ -0,0 +1,7 @@ +import * as Hammer from 'hammerjs'; +import {HammerGestureConfig} from '@angular/platform-browser'; +export class MyHammerConfig extends HammerGestureConfig { +overrides = { +'swipe': {direction: Hammer.DIRECTION_ALL} +} +} \ No newline at end of file diff --git a/src/app/quiz/quiz.page.html b/src/app/quiz/quiz.page.html index 8228fdf..1c93e22 100644 --- a/src/app/quiz/quiz.page.html +++ b/src/app/quiz/quiz.page.html @@ -33,7 +33,7 @@ - - - - + + + +
+ + +
\ No newline at end of file diff --git a/src/app/quiz/swipe-cards/swipe-cards.component.scss b/src/app/quiz/swipe-cards/swipe-cards.component.scss index 4a41486..e545831 100644 --- a/src/app/quiz/swipe-cards/swipe-cards.component.scss +++ b/src/app/quiz/swipe-cards/swipe-cards.component.scss @@ -10,7 +10,7 @@ } -ion-card { +.card { margin: 0; padding: 15px; @@ -27,4 +27,44 @@ ion-card { cursor: -webkit-grab; cursor: -moz-grab; cursor: grab; +} + + +.action-buttons { + display: flex; + position: fixed; + z-index: 1; + bottom: 10vh; + width: 120px; + left: calc(50% - 60px); + overflow: visible; + align-items: center; + justify-content: space-between; + height: 50px; + background-color: white; + box-shadow: 0px 0px 10px 0px $dark-blue; + + button { + width: 70px; + height: 70px; + background-color: white; + border-radius: 50%; + border: 0px; + font-size: 1.7rem; + display: flex; + align-items: center; + justify-content: center; + + &:first-child { + margin-left: -30px; + color: $pink; + box-shadow: -5px 0px 10px 0px $dark-blue; + } + + &:last-child { + margin-right: -30px; + color: $green; + box-shadow: 5px 0px 10px 0px $dark-blue; + } + } } \ No newline at end of file diff --git a/src/app/quiz/swipe-cards/swipe-cards.component.ts b/src/app/quiz/swipe-cards/swipe-cards.component.ts index d104392..9ea3f27 100644 --- a/src/app/quiz/swipe-cards/swipe-cards.component.ts +++ b/src/app/quiz/swipe-cards/swipe-cards.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, Input, ViewChildren, QueryList, ElementRef } from '@angular/core'; +import { Component, OnInit, Input, ViewChildren, QueryList, ElementRef, Renderer2, Output, EventEmitter } from '@angular/core'; @Component({ selector: 'app-swipe-cards', @@ -9,8 +9,129 @@ export class SwipeCardsComponent implements OnInit { @ViewChildren('tinderCard') tinderCards: QueryList; @Input() questions: Array; - constructor() { } + moveOutWidth: number; // value in pixels that a card needs to travel to dissapear from screen + shiftRequired: boolean; // state variable that indicates we need to remove the top card of the stack + transitionInProgress: boolean; // state variable that indicates currently there is transition on-going + heartVisible: boolean; + crossVisible: boolean; + + tinderCardsArray: Array; + + @Output() cardEvent = new EventEmitter(); + + + constructor( + private renderer: Renderer2 + ) { } ngOnInit() {} + ngAfterViewInit() { + this.moveOutWidth = document.documentElement.clientWidth * 1.5; + this.tinderCardsArray = this.tinderCards.toArray(); + this.tinderCards.changes.subscribe(()=>{ + this.tinderCardsArray = this.tinderCards.toArray(); + }); + } + + toggleChoiceIndicator(cross, heart) { + this.crossVisible = cross; + this.heartVisible = heart; + } + + handleShift() { + this.transitionInProgress = false; + this.toggleChoiceIndicator(false,false) + if (this.shiftRequired) { + this.shiftRequired = false; + this.questions.shift(); + }; + } + + handlePan(event) { + + if (event.deltaX === 0 || (event.center.x === 0 && event.center.y === 0) || !this.questions.length) return; + + if (this.transitionInProgress) { + this.handleShift(); + } + + this.renderer.addClass(this.tinderCardsArray[0].nativeElement, 'moving'); + + if (event.deltaX > 0) { this.toggleChoiceIndicator(false,true) } + if (event.deltaX < 0) { this.toggleChoiceIndicator(true,false) } + + let xMulti = event.deltaX * 0.03; + let yMulti = event.deltaY / 80; + let rotate = xMulti * yMulti; + + this.renderer.setStyle(this.tinderCardsArray[0].nativeElement, 'transform', 'translate(' + event.deltaX + 'px, ' + event.deltaY + 'px) rotate(' + rotate + 'deg)'); + + this.shiftRequired = true; + + } + + + handlePanEnd(event) { + + this.toggleChoiceIndicator(false,false); + + if (!this.questions.length) return; + + this.renderer.removeClass(this.tinderCardsArray[0].nativeElement, 'moving'); + + let keep = Math.abs(event.deltaX) < 80 || Math.abs(event.velocityX) < 0.5; + if (keep) { + + this.renderer.setStyle(this.tinderCardsArray[0].nativeElement, 'transform', ''); + this.shiftRequired = false; + + } else { + + let endX = Math.max(Math.abs(event.velocityX) * this.moveOutWidth, this.moveOutWidth); + let toX = event.deltaX > 0 ? endX : -endX; + let endY = Math.abs(event.velocityY) * this.moveOutWidth; + let toY = event.deltaY > 0 ? endY : -endY; + let xMulti = event.deltaX * 0.03; + let yMulti = event.deltaY / 80; + let rotate = xMulti * yMulti; + + this.renderer.setStyle(this.tinderCardsArray[0].nativeElement, 'transform', 'translate(' + toX + 'px, ' + (toY + event.deltaY) + 'px) rotate(' + rotate + 'deg)'); + + this.shiftRequired = true; + + console.log(!!(event.deltaX > 0), this.questions[0]); + + setTimeout(() => { + this.handleShift(); + }, 350); + } + + this.transitionInProgress = true; + } + + userClickedButton(event, heart) { + event.preventDefault(); + if (!this.questions.length) return false; + if (heart) { + this.tinderCardsArray[0].nativeElement.style.transform = 'translate(' + this.moveOutWidth + 'px, -100px) rotate(-30deg)'; + this.toggleChoiceIndicator(false,true); + console.log(!!(event.deltaX > 0), this.questions[0]); + + setTimeout(() => { + this.handleShift(); + }, 350); + } else { + this.tinderCardsArray[0].nativeElement.style.transform = 'translate(-' + this.moveOutWidth + 'px, -100px) rotate(30deg)'; + this.toggleChoiceIndicator(true,false); + console.log(!!(event.deltaX > 0), this.questions[0]); + + setTimeout(() => { + this.handleShift(); + }, 350); + }; + this.shiftRequired = true; + this.transitionInProgress = true; + } + } diff --git a/src/main.ts b/src/main.ts index 91ec6da..71a3aeb 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,6 @@ import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import 'hammerjs'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment';