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 #8919
fixes #8958
fixes #7934
  • Loading branch information
manucorporat authored and adamdbradley committed Nov 1, 2016
1 parent 033e1ea commit 04d61ee
Show file tree
Hide file tree
Showing 20 changed files with 273 additions and 148 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
2 changes: 1 addition & 1 deletion src/components/app/test/gesture-collision/page1.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
Menu
</ion-title>

<button ion-button menuToggle="right" right color="secondary">
<button ion-button menuToggle="right" right color="danger">
<ion-icon name="menu"></ion-icon>
</button>

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
21 changes: 15 additions & 6 deletions src/components/menu/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ export class Menu {
private _isPers: boolean = false;
private _init: boolean = false;
private _events: UIEventManager = new UIEventManager();
private _gestureID: number = 0;

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

/**
* @private
Expand Down Expand Up @@ -332,7 +337,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 @@ -471,7 +476,7 @@ export class Menu {
}

private _before() {
assert(this._isAnimating === false, '_before should be called when we are not animating');
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
Expand All @@ -483,8 +488,7 @@ export class Menu {
}

private _after(isOpen: boolean) {
assert(this._isAnimating === true, '_after should be called when we are animating');

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 @@ -494,8 +498,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 @@ -508,6 +514,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
23 changes: 14 additions & 9 deletions src/components/modal/test/basic/app-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,14 +147,16 @@ 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>ionViewWillLoad ({{called.ionViewWillLoad}})</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>ionViewWillLoad ({{called.ionViewWillLoad}})</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 @@ -519,10 +521,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 @@ -626,7 +630,8 @@ export class E2EApp {
],
imports: [
IonicModule.forRoot(E2EApp, {
statusbarPadding: true
statusbarPadding: true,
swipeBackEnabled: true
})
],
bootstrap: [IonicApp],
Expand Down
4 changes: 2 additions & 2 deletions src/components/range/range.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

import { clamp, isNumber, isPresent, isString, isTrueProperty } from '../../util/util';
import { Config } from '../../config/config';
import { Debouncer } from '../../util/debouncer';
import { TimeoutDebouncer } from '../../util/debouncer';
import { Form } from '../../util/form';
import { Ion } from '../ion';
import { Item } from '../item/item';
Expand Down Expand Up @@ -217,7 +217,7 @@ export class Range extends Ion implements AfterViewInit, ControlValueAccessor, O
_step: number = 1;
_snaps: boolean = false;

_debouncer: Debouncer = new Debouncer(0);
_debouncer: TimeoutDebouncer = new TimeoutDebouncer(0);
_events: UIEventManager = new UIEventManager();
/**
* @private
Expand Down
4 changes: 2 additions & 2 deletions src/components/searchbar/searchbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { NgControl } from '@angular/forms';
import { Config } from '../../config/config';
import { Ion } from '../ion';
import { isPresent, isTrueProperty } from '../../util/util';
import { Debouncer } from '../../util/debouncer';
import { TimeoutDebouncer } from '../../util/debouncer';


/**
Expand Down Expand Up @@ -61,7 +61,7 @@ export class Searchbar extends Ion {
_autocomplete: string = 'off';
_autocorrect: string = 'off';
_isActive: boolean = false;
_debouncer: Debouncer = new Debouncer(250);
_debouncer: TimeoutDebouncer = new TimeoutDebouncer(250);

/**
* @input {string} The predefined color to use. For example: `"primary"`, `"secondary"`, `"danger"`.
Expand Down
8 changes: 7 additions & 1 deletion src/components/tabs/test/advanced/app-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@ export class Tab1Page1 {
templateUrl: './tab1page2.html'
})
export class Tab1Page2 {
constructor(public tabs: Tabs) { }

favoritesTab() {
// TODO fix this with tabsHideOnSubPages=true
this.tabs.select(1);
}

ionViewWillEnter() {
console.log('Tab1Page2, ionViewWillEnter');
Expand Down Expand Up @@ -346,7 +352,7 @@ export const deepLinkConfig: DeepLinkConfig = {
Tab3Page1
],
imports: [
IonicModule.forRoot(E2EApp, null, deepLinkConfig)
IonicModule.forRoot(E2EApp, {tabsHideOnSubPages: true}, deepLinkConfig)
],
bootstrap: [IonicApp],
entryComponents: [
Expand Down
1 change: 1 addition & 0 deletions src/components/tabs/test/advanced/tab1page2.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<ion-content padding>

<p><button ion-button navPush="Tab1Page3">Go to Tab 1, Page 3</button></p>
<p><button ion-button (click)="favoritesTab()">Favorites Tab</button></p>
<p><button ion-button class="e2eBackToTab1Page1" navPop>Back to Tab 1, Page 1</button></p>
<div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div>
<div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div>
Expand Down
Loading

0 comments on commit 04d61ee

Please sign in to comment.