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
fixes ionic-team#7934
  • Loading branch information
manucorporat committed Nov 1, 2016
1 parent 855f137 commit 1bbcc4f
Show file tree
Hide file tree
Showing 14 changed files with 247 additions and 124 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
2 changes: 1 addition & 1 deletion src/components/app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export class App {
// listen for hardware back button events
// register this back button action with a default priority
_platform.registerBackButtonAction(this.navPop.bind(this));
this._canDisableScroll = _config.get('canDisableScroll', true);
this._canDisableScroll = _config.get('canDisableScroll', false);
}

/**
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;
}
1 change: 0 additions & 1 deletion src/components/menu/menu-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@ export class MenuController {
}
return menu.open();
}

return Promise.resolve(false);
}

Expand Down
4 changes: 3 additions & 1 deletion src/components/menu/menu-gestures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { SlideEdgeGesture } from '../../gestures/slide-edge-gesture';
import { SlideData } from '../../gestures/slide-gesture';
import { assign } from '../../util/util';
import { GestureController, GesturePriority } from '../../gestures/gesture-controller';
import { NativeRafDebouncer } from '../../util/debouncer';

/**
* Gesture attached to the content which the menu is assigned to
Expand All @@ -11,15 +12,16 @@ export class MenuContentGesture extends SlideEdgeGesture {

constructor(
public menu: Menu,
gestureCtrl: GestureController,
contentEle: HTMLElement,
gestureCtrl: GestureController,
options: any = {}) {
super(contentEle, assign({
direction: 'x',
edge: menu.side,
threshold: 0,
maxEdgeStart: menu.maxEdgeStart || 50,
maxAngle: 40,
debouncer: new NativeRafDebouncer(),
gesture: gestureCtrl.create('menu-swipe', {
priority: GesturePriority.MenuSwipe,
})
Expand Down
19 changes: 15 additions & 4 deletions src/components/menu/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, ContentChild, ElementRef, EventEmit

import { Backdrop } from '../backdrop/backdrop';
import { Config } from '../../config/config';
import { isTrueProperty } from '../../util/util';
import { isTrueProperty, assert } from '../../util/util';
import { Keyboard } from '../../util/keyboard';
import { MenuContentGesture } from './menu-gestures';
import { MenuController } from './menu-controller';
Expand Down Expand Up @@ -199,6 +199,7 @@ export class Menu {
private _isPers: boolean = false;
private _init: boolean = false;
private _events: UIEventManager = new UIEventManager();
private _gestureID: number;

/**
* @private
Expand Down Expand Up @@ -304,7 +305,9 @@ export class Menu {
private _keyboard: Keyboard,
private _zone: NgZone,
private _gestureCtrl: GestureController
) {}
) {
this._gestureID = _gestureCtrl.newID();
}

/**
* @private
Expand Down Expand Up @@ -333,7 +336,7 @@ export class Menu {
this.setElementAttribute('type', this.type);

// add the gestures
this._cntGesture = new MenuContentGesture(this, this._gestureCtrl, document.body);
this._cntGesture = new MenuContentGesture(this, document.body, this._gestureCtrl);

// register listeners if this menu is enabled
// check if more than one menu is on the same side
Expand Down Expand Up @@ -472,6 +475,8 @@ export class Menu {
}

private _before() {
assert(!this._isAnimating, '_before() should not be called while animating');

// this places the menu into the correct location before it animates in
// this css class doesn't actually kick off any animations
this.menuContent && this.menuContent.resize();
Expand All @@ -482,6 +487,7 @@ export class Menu {
}

private _after(isOpen: boolean) {
assert(this._isAnimating, '_before() should be called while animating');
// keep opening/closing the menu disabled for a touch more yet
// only add listeners/css if it's enabled and isOpen
// and only remove listeners/css if it's not open
Expand All @@ -491,8 +497,10 @@ export class Menu {

this._events.unlistenAll();
if (isOpen) {
this._cntEle.classList.add('menu-content-open');
// Disable swipe to go back gesture
this._gestureCtrl.disableGesture('goback-swipe', this._gestureID);

this._cntEle.classList.add('menu-content-open');
let callback = this.onBackdropClick.bind(this);
this._events.pointerEvents({
element: this._cntEle,
Expand All @@ -505,6 +513,9 @@ export class Menu {
this.ionOpen.emit(true);

} else {
// Enable swipe to go back gesture
this._gestureCtrl.enableGesture('goback-swipe', this._gestureID);

this._cntEle.classList.remove('menu-content-open');
this.setElementClass('show-menu', false);
this.backdrop.setElementClass('show-menu', false);
Expand Down
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
2 changes: 1 addition & 1 deletion src/components/tabs/test/advanced/app-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ export const deepLinkConfig: DeepLinkConfig = {
Tab3Page1
],
imports: [
IonicModule.forRoot(E2EApp, null, deepLinkConfig)
IonicModule.forRoot(E2EApp, {tabsHideOnSubPages: true}, deepLinkConfig)
],
bootstrap: [IonicApp],
entryComponents: [
Expand Down
Loading

0 comments on commit 1bbcc4f

Please sign in to comment.