Skip to content

Commit

Permalink
fix(nav): swipe to go back gesture
Browse files Browse the repository at this point in the history
- smoother by debouncing touch events (reduces bank)
- dynamic animation duration
- intelligent behavior based in the position, speed and direccion of the swipe (sharing logic with sliding item)

fixes ionic-team#8919
fixes ionic-team#8958
  • Loading branch information
manucorporat committed Oct 31, 2016
1 parent 855f137 commit 415f607
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 83 deletions.
32 changes: 28 additions & 4 deletions src/animations/animation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -861,9 +861,32 @@ export class Animation {
* Start the animation with a user controlled progress.
*/
progressStart() {
// ensure all past transition end events have been cleared
this._clearAsync();

// fire off all the "before" function that have DOM READS in them
// elements will be in the DOM, however visibily hidden
// so we can read their dimensions if need be
// ******** DOM READ ****************
this._beforeReadFn();

// fire off all the "before" function that have DOM WRITES in them
// ******** DOM WRITE ****************
this._beforeWriteFn();

// ******** DOM WRITE ****************
this._progressStart();
}

/**
* @private
* DOM WRITE
* RECURSION
*/
_progressStart() {
for (var i = 0; i < this._cL; i++) {
// ******** DOM WRITE ****************
this._c[i].progressStart();
this._c[i]._progressStart();
}

// ******** DOM WRITE ****************
Expand Down Expand Up @@ -907,13 +930,14 @@ export class Animation {
/**
* End the progress animation.
*/
progressEnd(shouldComplete: boolean, currentStepValue: number) {
progressEnd(shouldComplete: boolean, currentStepValue: number, maxDelta: number = 0) {
console.debug('Animation, progressEnd, shouldComplete', shouldComplete, 'currentStepValue', currentStepValue);

this._isAsync = (currentStepValue > 0.05 && currentStepValue < 0.95);

const dur = 64;
const stepValue = shouldComplete ? 1 : 0;
const factor = Math.max(Math.abs(currentStepValue - stepValue), 0.5) * 2;
const dur = 64 + factor * maxDelta;

this._progressEnd(shouldComplete, stepValue, dur, this._isAsync);

Expand All @@ -922,7 +946,7 @@ export class Animation {
// set the async TRANSITION END event
// and run onFinishes when the transition ends
// ******** DOM WRITE ****************
this._asyncEnd(dur, true);
this._asyncEnd(dur, shouldComplete);

// this animation has a duration so we need another RAF
// for the CSS TRANSITION properties to kick in
Expand Down
25 changes: 3 additions & 22 deletions src/components/item/item-sliding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, ContentChildren, ContentChild, Dire

import { CSS, nativeRaf, nativeTimeout, clearNativeTimeout } from '../../util/dom';
import { Item } from './item';
import { isPresent, assert } from '../../util/util';
import { isPresent, swipeShouldReset, assert } from '../../util/util';
import { List } from '../list/list';

const SWIPE_MARGIN = 30;
Expand Down Expand Up @@ -320,10 +320,10 @@ export class ItemSliding {

// Check if the drag didn't clear the buttons mid-point
// and we aren't moving fast enough to swipe open
let isCloseDirection = (this._openAmount > 0) === !(velocity < 0);
let isResetDirection = (this._openAmount > 0) === !(velocity < 0);
let isMovingFast = Math.abs(velocity) > 0.3;
let isOnCloseZone = Math.abs(this._openAmount) < Math.abs(restingPoint / 2);
if (shouldClose(isCloseDirection, isMovingFast, isOnCloseZone)) {
if (swipeShouldReset(isResetDirection, isMovingFast, isOnCloseZone)) {
restingPoint = 0;
}

Expand Down Expand Up @@ -463,22 +463,3 @@ export class ItemSliding {
this._renderer.setElementClass(this._elementRef.nativeElement, cssClass, shouldAdd);
}
}

function shouldClose(isCloseDirection: boolean, isMovingFast: boolean, isOnCloseZone: boolean): boolean {
// The logic required to know when the sliding item should close (openAmount=0)
// depends on three booleans (isCloseDirection, isMovingFast, isOnCloseZone)
// and it ended up being too complicated to be written manually without errors
// so the truth table is attached below: (0=false, 1=true)
// isCloseDirection | isMovingFast | isOnCloseZone || shouldClose
// 0 | 0 | 0 || 0
// 0 | 0 | 1 || 1
// 0 | 1 | 0 || 0
// 0 | 1 | 1 || 0
// 1 | 0 | 0 || 0
// 1 | 0 | 1 || 1
// 1 | 1 | 0 || 1
// 1 | 1 | 1 || 1
// The resulting expression was generated by resolving the K-map (Karnaugh map):
let shouldClose = (!isMovingFast && isOnCloseZone) || (isCloseDirection && isMovingFast);
return shouldClose;
}
21 changes: 13 additions & 8 deletions src/components/modal/test/basic/app-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,15 @@ export class E2EPage {
</ion-item>
</ion-list>
<button ion-button full (click)="submit()">Submit</button>
<p>ionViewCanEnter ({{called.ionViewCanEnter}})</p>
<p>ionViewCanLeave ({{called.ionViewCanLeave}})</p>
<p>ionViewDidLoad ({{called.ionViewDidLoad}})</p>
<p>ionViewWillEnter ({{called.ionViewWillEnter}})</p>
<p>ionViewDidEnter ({{called.ionViewDidEnter}})</p>
<p>ionViewWillLeave ({{called.ionViewWillLeave}})</p>
<p>ionViewDidLeave ({{called.ionViewDidLeave}})</p>
<div padding>
<p>ionViewCanEnter ({{called.ionViewCanEnter}})</p>
<p>ionViewCanLeave ({{called.ionViewCanLeave}})</p>
<p>ionViewDidLoad ({{called.ionViewDidLoad}})</p>
<p>ionViewWillEnter ({{called.ionViewWillEnter}})</p>
<p>ionViewDidEnter ({{called.ionViewDidEnter}})</p>
<p>ionViewWillLeave ({{called.ionViewWillLeave}})</p>
<p>ionViewDidLeave ({{called.ionViewDidLeave}})</p>
</div>
</ion-content>
`,
providers: [SomeComponentProvider]
Expand Down Expand Up @@ -505,10 +507,12 @@ export class ModalFirstPage {
}

ionViewWillLeave() {
console.log('ModalFirstPage ionViewWillLeave fired');
this.called.ionViewWillLeave++;
}

ionViewDidLeave() {
console.log('ModalFirstPage ionViewDidLeave fired');
this.called.ionViewDidLeave++;
}

Expand Down Expand Up @@ -612,7 +616,8 @@ export class E2EApp {
],
imports: [
IonicModule.forRoot(E2EApp, {
statusbarPadding: true
statusbarPadding: true,
swipeBackEnabled: true
})
],
bootstrap: [IonicApp],
Expand Down
34 changes: 24 additions & 10 deletions src/gestures/drag-gesture.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { defaults } from '../util/util';
import { GestureDelegate } from '../gestures/gesture-controller';
import { PanRecognizer } from './recognizers';
import { PointerEvents, UIEventManager } from '../util/ui-event-manager';
import { PointerEvents, PointerEventsConfig, UIEventManager } from '../util/ui-event-manager';
import { pointerCoord } from '../util/dom';

/**
Expand All @@ -12,6 +12,8 @@ export interface PanGestureConfig {
maxAngle?: number;
direction?: 'x' | 'y';
gesture?: GestureDelegate;
zone?: boolean;
capture?: boolean;
}

/**
Expand All @@ -26,38 +28,50 @@ export class PanGesture {
public isListening: boolean = false;
protected gestute: GestureDelegate;
protected direction: string;
private eventsConfig: PointerEventsConfig;

constructor(private element: HTMLElement, opts: PanGestureConfig = {}) {
defaults(opts, {
threshold: 20,
maxAngle: 40,
direction: 'x'
direction: 'x',
zone: true,
capture: false,
});

this.gestute = opts.gesture;
this.direction = opts.direction;
this.eventsConfig = {
element: this.element,
pointerDown: this.pointerDown.bind(this),
pointerMove: this.pointerMove.bind(this),
pointerUp: this.pointerUp.bind(this),
zone: opts.zone,
capture: opts.capture
};
this.detector = new PanRecognizer(opts.direction, opts.threshold, opts.maxAngle);
}

listen() {
if (!this.isListening) {
this.pointerEvents = this.events.pointerEvents({
element: this.element,
pointerDown: this.pointerDown.bind(this),
pointerMove: this.pointerMove.bind(this),
pointerUp: this.pointerUp.bind(this),
});
this.isListening = true;
if (this.isListening) {
return;
}
this.pointerEvents = this.events.pointerEvents(this.eventsConfig);
this.isListening = true;
}

unlisten() {
if (!this.isListening) {
return;
}
this.gestute && this.gestute.release();
this.events.unlistenAll();
this.isListening = false;
}

destroy() {
this.gestute && this.gestute.destroy();
this.gestute = null;
this.unlisten();
this.element = null;
}
Expand Down
Loading

0 comments on commit 415f607

Please sign in to comment.