feat(nav): iOS swipe to go back

Closes #5185
This commit is contained in:
Adam Bradley
2016-02-17 13:35:30 -06:00
parent ae17e7f988
commit da37029195
6 changed files with 226 additions and 235 deletions

View File

@ -243,7 +243,7 @@ export class Animation {
// set the async TRANSITION END event
// and run onFinishes when the transition ends
self._asyncEnd(duration);
self._asyncEnd(duration, true);
// begin each animation when everything is rendered in their place
// and the transition duration/easing is ready to go
@ -275,7 +275,7 @@ export class Animation {
// since there was no animation, it's done
// fire off all the onFinishes
self._onFinish();
self._onFinish(true);
}
}
@ -299,7 +299,7 @@ export class Animation {
// set the async TRANSITION END event
// and run onFinishes when the transition ends
self._asyncEnd(duration);
self._asyncEnd(duration, false);
} else {
// this animation does not have a duration, so it should not animate
@ -308,15 +308,15 @@ export class Animation {
// since there was no animation, it's done
// fire off all the onFinishes
self._onFinish();
self._onFinish(false);
}
}
_asyncEnd(duration: number) {
_asyncEnd(duration: number, shouldComplete: boolean) {
var self = this;
function onTransitionEnd(ev) {
console.debug('Animation async end,', (ev ? 'transitionEnd event' : 'fallback timeout'));
console.debug('Animation async end,', (ev ? 'transitionEnd, ' + ev.target.nodeName + ', property: ' + ev.propertyName : 'fallback timeout'));
// ensure transition end events and timeouts have been cleared
self._clearAsync();
@ -324,7 +324,7 @@ export class Animation {
// set the after styles
self._after();
self._willChange(false);
self._onFinish();
self._onFinish(shouldComplete);
}
// set the TRANSITION END event on one of the transition elements
@ -580,12 +580,12 @@ export class Animation {
// for example, the left menu was dragged all the way open already
this._after();
this._willChange(false);
this._onFinish();
this._onFinish(shouldComplete);
} else {
// the stepValue was left off at a point when it needs to finish transition still
// for example, the left menu was opened 75% and needs to finish opening
this._asyncEnd(64);
this._asyncEnd(64, shouldComplete);
// force quick duration, linear easing
this._setTrans(64, true);
@ -611,15 +611,15 @@ export class Animation {
return this;
}
_onFinish() {
_onFinish(hasCompleted: boolean) {
this.isPlaying = false;
var i;
for (i = 0; i < this._fFns.length; i++) {
this._fFns[i]();
this._fFns[i](hasCompleted);
}
for (i = 0; i < this._fOnceFns.length; i++) {
this._fOnceFns[i]();
this._fOnceFns[i](hasCompleted);
}
this._fOnceFns = [];
}

View File

@ -1,4 +1,4 @@
import {Compiler, ElementRef, Injector, provide, NgZone, AppViewManager, Renderer, ResolvedProvider, Type} from 'angular2/core';
import {Compiler, ElementRef, Injector, provide, NgZone, AppViewManager, Renderer, ResolvedProvider, Type, Input} from 'angular2/core';
import {wtfLeave, wtfCreateScope, WtfScopeFn, wtfStartTimeRange, wtfEndTimeRange} from 'angular2/instrumentation';
import {Config} from '../../config/config';
@ -7,7 +7,7 @@ import {IonicApp} from '../app/app';
import {Keyboard} from '../../util/keyboard';
import {NavParams} from './nav-params';
import {NavRouter} from './nav-router';
import {pascalCaseToDashCase} from '../../util/util';
import {pascalCaseToDashCase, isTrueProperty} from '../../util/util';
import {raf} from '../../util/dom';
import {SwipeBackGesture} from './swipe-back';
import {Transition} from '../../transitions/transition';
@ -106,11 +106,12 @@ import {ViewController} from './view-controller';
export class NavController extends Ion {
private _transIds = 0;
private _init = false;
private _lastTrans: Transition;
private _trans: Transition;
private _sbGesture: SwipeBackGesture;
private _sbEnabled: boolean;
private _sbThreshold: number;
protected _ids: number = -1;
protected _sbEnabled: any;
protected _sbThreshold: any;
protected _sbTrans: Transition = null;
protected _trnsDelay: any;
protected _trnsTime: number = 0;
protected _views: Array<ViewController> = [];
@ -133,12 +134,7 @@ export class NavController extends Ion {
/**
* @private
*/
sbGesture: any;
/**
* @private
*/
parent;
parent: any;
/**
* @private
@ -164,7 +160,7 @@ export class NavController extends Ion {
this._trnsDelay = config.get('pageTransitionDelay');
this._sbEnabled = config.get('swipeBackEnabled') || false;
this._sbEnabled = config.getBoolean('swipeBackEnabled') || false;
this._sbThreshold = config.get('swipeBackThreshold') || 40;
this.id = ++ctrlIds;
@ -261,7 +257,7 @@ export class NavController extends Ion {
*/
setPages(pages: Array<{page: Type, params?: any}>, opts: NavOptions = {}): Promise<any> {
if (!pages || !pages.length) {
return Promise.resolve();
return Promise.resolve(false);
}
// deprecated warning
@ -296,9 +292,9 @@ export class NavController extends Ion {
let promise = new Promise(res => { resolve = res; });
// start the transition, fire resolve when done...
this._transition(enteringView, leavingView, opts, () => {
this._transition(enteringView, leavingView, opts, (hasCompleted: boolean) => {
// transition has completed!!
resolve(enteringView);
resolve(hasCompleted);
});
return promise;
@ -533,9 +529,9 @@ export class NavController extends Ion {
let leavingView = this.getByState(STATE_INIT_LEAVE);
// start the transition, fire resolve when done...
this._transition(enteringView, leavingView, opts, () => {
this._transition(enteringView, leavingView, opts, (hasCompleted: boolean) => {
// transition has completed!!
resolve(enteringView);
resolve(hasCompleted);
});
return promise;
@ -697,21 +693,21 @@ export class NavController extends Ion {
opts.animation = forcedActive.getTransitionName(opts.direction);
}
if (this._lastTrans) {
this._lastTrans
if (this._trans) {
this._trans
.onFinish(() => {
opts.animate = false;
this._transition(forcedActive, null, opts, () => {
this._transition(forcedActive, null, opts, (hasCompleted: boolean) => {
// transition has completed!!
resolve();
resolve(hasCompleted);
});
}, false, true)
.stop();
this._lastTrans.destroy();
this._lastTrans = null;
this._trans.destroy();
this._trans = null;
} else {
resolve();
resolve(false);
}
return promise;
@ -732,9 +728,9 @@ export class NavController extends Ion {
let enteringView = this.getByState(STATE_INIT_ENTER);
// start the transition, fire resolve when done...
this._transition(enteringView, leavingView, opts, () => {
this._transition(enteringView, leavingView, opts, (hasCompleted: boolean) => {
// transition has completed!!
resolve();
resolve(hasCompleted);
});
return promise;
@ -744,7 +740,7 @@ export class NavController extends Ion {
// there's still an active view after _remove() figured out states
// so this means views that were only removed before the active
// view, so auto-resolve since no transition needs to happen
return Promise.resolve();
return Promise.resolve(false);
}
/**
@ -856,8 +852,8 @@ export class NavController extends Ion {
if (enteringView === leavingView) {
// if the entering view and leaving view are the same thing don't continue
this._transComplete(transId, enteringView, leavingView, null);
return done(enteringView);
this._transFinish(transId, enteringView, leavingView, null, false);
return done(false);
}
// lets time this sucker, ready go
@ -888,10 +884,10 @@ export class NavController extends Ion {
*/
// begin the multiple async process of transitioning to the entering view
this._render(transId, enteringView, leavingView, opts, () => {
this._transComplete(transId, enteringView, leavingView, opts.direction);
this._render(transId, enteringView, leavingView, opts, (hasCompleted: boolean) => {
this._transFinish(transId, enteringView, leavingView, opts.direction, hasCompleted);
wtfEndTimeRange(wtfScope);
done(enteringView);
done(hasCompleted);
});
}
@ -985,19 +981,13 @@ export class NavController extends Ion {
enteringView.willEnter();
leavingView.willLeave();
// lifecycle events may have updated some data
// wait one frame and allow the raf to do a change detection
// before kicking off the transition and showing the new view
raf(() => {
this._beforeTrans(enteringView, leavingView, opts, done);
});
} else {
// this view is being preloaded, don't call lifecycle events
// transition does not need to animate
opts.animate = false;
this._beforeTrans(enteringView, leavingView, opts, done);
}
this._beforeTrans(enteringView, leavingView, opts, done);
}
/**
@ -1034,8 +1024,8 @@ export class NavController extends Ion {
leavingView,
transitionOpts);
this._lastTrans && this._lastTrans.destroy();
this._lastTrans = transAnimation;
this._trans && this._trans.destroy();
this._trans = transAnimation;
if (opts.animate === false) {
// force it to not animate the elements, just apply the "to" styles
@ -1054,24 +1044,34 @@ export class NavController extends Ion {
}
// create a callback for when the animation is done
transAnimation.onFinish(() => {
transAnimation.onFinish((hasCompleted: boolean) => {
// transition animation has ended
// dispose the animation and it's element references
// destroy the animation and it's element references
transAnimation.destroy();
this._afterTrans(enteringView, leavingView, opts, done);
this._afterTrans(enteringView, leavingView, opts, hasCompleted, done);
});
// cool, let's do this, start the transition
transAnimation.play();
if (opts.progressAnimation) {
// this is a swipe to go back, just get the transition progress ready
// kick off the swipe animation start
transAnimation.progressStart();
} else {
// this is a normal animation
// kick it off and let it play through
transAnimation.play();
}
});
}
/**
* @private
*/
private _afterTrans(enteringView: ViewController, leavingView: ViewController, opts: NavOptions, done: Function) {
private _afterTrans(enteringView: ViewController, leavingView: ViewController, opts: NavOptions, hasCompleted: boolean, done: Function) {
// transition has completed, update each view's state
// place back into the zone, run didEnter/didLeave
// call the final callback when done
@ -1079,7 +1079,7 @@ export class NavController extends Ion {
// run inside of the zone again
this._zone.run(() => {
if (!opts.preload) {
if (!opts.preload && hasCompleted) {
enteringView.didEnter();
leavingView.didLeave();
}
@ -1087,7 +1087,7 @@ export class NavController extends Ion {
if (enteringView.state === STATE_INACTIVE) {
// this entering view is already set to inactive, so this
// transition must be canceled, so don't continue
return done();
return done(hasCompleted);
}
if (opts.keyboardClose !== false && this._keyboard.isOpen()) {
@ -1097,12 +1097,12 @@ export class NavController extends Ion {
this._keyboard.onClose(() => {
// keyboard has finished closing, transition complete
done();
done(hasCompleted);
}, 32);
} else {
// all good, transition complete
done();
done(hasCompleted);
}
});
}
@ -1110,51 +1110,67 @@ export class NavController extends Ion {
/**
* @private
*/
private _transComplete(transId: number, enteringView: ViewController, leavingView: ViewController, direction: string) {
private _transFinish(transId: number, enteringView: ViewController, leavingView: ViewController, direction: string, hasCompleted: boolean) {
// a transition has completed, but not sure if it's the last one or not
// check if this transition is the most recent one or not
if (transId === this._transIds) {
// ok, good news, there were no other transitions that kicked
// off during the time this transition started and ended
// so the entering one is now officially the active transition
// and the leaving transition is now just inactive
if (enteringView.state !== STATE_REMOVE_AFTER_TRANS) {
enteringView.state = STATE_ACTIVE;
}
if (hasCompleted) {
// this transition has completed as normal
// so the entering one is now the active view
// and the leaving view is now just inactive
if (enteringView.state !== STATE_REMOVE_AFTER_TRANS) {
enteringView.state = STATE_ACTIVE;
}
if (leavingView.state !== STATE_REMOVE_AFTER_TRANS) {
leavingView.state = STATE_INACTIVE;
}
if (leavingView.state !== STATE_REMOVE_AFTER_TRANS) {
leavingView.state = STATE_INACTIVE;
}
// only need to do all this clean up if the transition
// completed, otherwise nothing actually changed
// destroy all of the views that come after the active view
this._cleanup();
// destroy all of the views that come after the active view
this._cleanup();
// make sure only this entering view and PREVIOUS view are the
// only two views that are not display:none
leavingView = this.getPrevious(enteringView);
this._views.forEach(view => {
let shouldShow = (view === enteringView) || (view === leavingView);
view.domCache(shouldShow, this._renderer);
});
// make sure only this entering view and PREVIOUS view are the
// only two views that are not display:none
leavingView = this.getPrevious(enteringView);
this._views.forEach(view => {
let shouldShow = (view === enteringView) || (view === leavingView);
view.domCache(shouldShow, this._renderer);
});
// this check only needs to happen once, which will add the css
// class to the nav when it's finished its first transition
if (!this._init) {
this._init = true;
this._renderer.setElementClass(this.elementRef.nativeElement, 'has-views', true);
}
// this check only needs to happen once, which will add the css
// class to the nav when it's finished its first transition
if (!this._init) {
this._init = true;
this._renderer.setElementClass(this.elementRef.nativeElement, 'has-views', true);
} else {
// this transition has not completed, meaning the
// entering view did not end up as the active view
// this would happen when swipe to go back started
// but the user did not complete the swipe and the
// what was the active view stayed as the active view
leavingView.state = STATE_ACTIVE;
enteringView.state = STATE_INACTIVE;
}
// allow clicks and enable the app again
this._app && this._app.setEnabled(true);
this.setTransitioning(false);
if (this.router && direction !== null) {
if (this.router && direction !== null && hasCompleted) {
// notify router of the state change if a direction was provided
this.router.stateChange(direction, enteringView);
}
// see if we should add the swipe back gesture listeners or not
this._sbCheck();
} else {
// darn, so this wasn't the most recent transition
// so while this one did end, there's another more recent one
@ -1189,6 +1205,7 @@ export class NavController extends Ion {
this._views.splice(this.indexOf(view), 1);
view.destroy();
});
}
/**
@ -1269,156 +1286,100 @@ export class NavController extends Ion {
* @private
*/
swipeBackStart() {
return;
if (!this._app.isEnabled() || !this.canSwipeBack()) {
return;
}
// disables the app during the transition
this._app.setEnabled(false);
this.setTransitioning(true);
// default the direction to "back"
let opts = {
direction: 'back'
let opts: NavOptions = {
direction: 'back',
progressAnimation: true
};
// get the active view and set that it is staged to be leaving
// was probably the one popped from the stack
let leavingView = this.getActive() || new ViewController();
leavingView.willLeave();
leavingView.willUnload();
// figure out the states of each view in the stack
let leavingView = this._remove(this._views.length - 1, 1);
// the entering view is now the new last view
let enteringView = this.getPrevious(leavingView);
enteringView.willEnter();
if (leavingView) {
opts.animation = leavingView.getTransitionName(opts.direction);
// wait for the new view to complete setup
this._render(0, enteringView, leavingView, {}, () => {
// get the view thats ready to enter
let enteringView = this.getByState(STATE_INIT_ENTER);
});
// start the transition, fire callback when done...
this._transition(enteringView, leavingView, opts, (hasCompleted: boolean) => {
// swipe back has finished!!
console.debug('swipeBack, hasCompleted', hasCompleted);
});
}
}
/**
* @private
*/
swipeBackProgress(value) {
return;
if (this._sbTrans) {
swipeBackProgress(stepValue: number) {
if (this._trans && this._sbGesture) {
// continue to disable the app while actively dragging
this._app.setEnabled(false, 4000);
this.setTransitioning(true, 4000);
// set the transition animation's progress
this._sbTrans.progressStep(value);
this._trans.progressStep(stepValue);
}
}
/**
* @private
*/
swipeBackEnd(completeSwipeBack, rate) {
return;
if (!this._sbTrans) return;
// disables the app during the transition
this._app.setEnabled(false);
this.setTransitioning(true);
this._sbTrans.onFinish(() => {
this._zone.run(() => {
// find the views that were entering and leaving
let enteringView = null;// this._getStagedEntering();
let leavingView = null;//this._getStagedLeaving();
if (enteringView && leavingView) {
// finish up the animation
if (completeSwipeBack) {
// swipe back has completed navigating back
// update each view's state
enteringView.state = STATE_ACTIVE;
leavingView.state = STATE_INACTIVE;
enteringView.didEnter();
leavingView.didLeave();
if (this.router) {
// notify router of the pop state change
this.router.stateChange('pop', enteringView);
}
} else {
// cancelled the swipe back, they didn't end up going back
// return views to their original state
leavingView.state = STATE_ACTIVE;
enteringView.state = STATE_INACTIVE;
leavingView.willEnter();
leavingView.didEnter();
enteringView.didLeave();
leavingView.shouldDestroy = false;
enteringView.shouldDestroy = false;
}
}
// empty out and dispose the swipe back transition animation
this._sbTrans && this._sbTrans.destroy();
this._sbTrans = null;
// all done!
//this._transComplete();
});
}, true);
this._sbTrans.progressEnd(completeSwipeBack, 0.5);
swipeBackEnd(shouldComplete: boolean, currentStepValue: number) {
if (this._trans && this._sbGesture) {
// the swipe back gesture has ended
this._trans.progressEnd(shouldComplete, currentStepValue);
}
}
/**
* @private
*/
private _sbComplete() {
return;
if (this.canSwipeBack()) {
// it is possible to swipe back
private _sbCheck() {
if (this._sbEnabled) {
// this nav controller can have swipe to go back
if (this.sbGesture) {
// this is already an active gesture, don't create another one
return;
if (!this._sbGesture) {
// create the swipe back gesture if we haven't already
let opts = {
edge: 'left',
threshold: this._sbThreshold
};
this._sbGesture = new SwipeBackGesture(this.getNativeElement(), opts, this);
}
let opts = {
edge: 'left',
threshold: this._sbThreshold
};
this.sbGesture = new SwipeBackGesture(this.getNativeElement(), opts, this);
console.debug('SwipeBackGesture listen');
this.sbGesture.listen();
if (this.canSwipeBack()) {
// it is be possible to swipe back
if (!this._sbGesture.isListening) {
this._zone.runOutsideAngular(() => {
// start listening if it's not already
console.debug('swipeBack gesture, listen');
this._sbGesture.listen();
});
}
} else if (this.sbGesture) {
// it is not possible to swipe back and there is an
// active sbGesture, so unlisten it
console.debug('SwipeBackGesture unlisten');
this.sbGesture.unlisten();
this.sbGesture = null;
} else if (this._sbGesture.isListening) {
// it should not be possible to swipe back
// but the gesture is still listening
console.debug('swipeBack gesture, unlisten');
this._sbGesture.unlisten();
}
}
}
/**
* Check to see if swipe-to-go-back is enabled
* @param {boolean} isSwipeBackEnabled Set whether or not swipe-to-go-back is enabled
* @returns {boolean} Whether swipe-to-go-back is enabled
* @input {boolean} Whether it's possible to swipe-to-go-back on this nav controller or not.
*/
isSwipeBackEnabled(val?: boolean): boolean {
if (arguments.length) {
this._sbEnabled = !!val;
}
@Input()
get swipeBackEnabled(): boolean {
return this._sbEnabled;
}
set swipeBackEnabled(val: boolean) {
this._sbEnabled = isTrueProperty(val);
}
/**
* If it's possible to use swipe back or not. If it's not possible
* to go back, or swipe back is not enable then this will return false.
@ -1427,7 +1388,7 @@ export class NavController extends Ion {
* @returns {boolean} Whether you can swipe to go back
*/
canSwipeBack(): boolean {
return (this._sbEnabled && this.canGoBack());
return (this._sbEnabled && !this.isTransitioning() && this._app.isEnabled() && this.canGoBack());
}
/**
@ -1594,7 +1555,8 @@ export interface NavOptions {
keyboardClose?: boolean;
preload?: boolean;
transitionDelay?: number;
postLoad?: Function
postLoad?: Function;
progressAnimation?: boolean;
}
const STATE_ACTIVE = 'active';

View File

@ -1,4 +1,4 @@
import {Component, ElementRef, Input, Optional, NgZone, Compiler, AppViewManager, Renderer, Type, ViewChild} from 'angular2/core';
import {Component, ElementRef, Input, Optional, NgZone, Compiler, AppViewManager, Renderer, Type} from 'angular2/core';
import {IonicApp} from '../app/app';
import {Config} from '../../config/config';
@ -98,7 +98,7 @@ import {ViewController} from './view-controller';
* </pre>
* </div>
*
* @demo /docs/v2/demos/navigation/
* @demo /docs/v2/demos/navigation/
* @see {@link /docs/v2/components#navigation Navigation Component Docs}
*/
@Component({
@ -112,11 +112,6 @@ export class Nav extends NavController {
*/
@Input() root: Type;
/**
* @private
*/
@Input() swipeBackEnabled: any;
constructor(
@Optional() hostNavCtrl: NavController,
@Optional() viewCtrl: ViewController,
@ -149,9 +144,6 @@ export class Nav extends NavController {
}
this.push(this.root);
}
// default the swipe back to be enabled
this.isSwipeBackEnabled( (this.swipeBackEnabled || '').toString() !== 'false' );
}
}

View File

@ -1,36 +1,57 @@
import {NavController} from './nav-controller';
import {SlideEdgeGesture} from '../../gestures/slide-edge-gesture';
import {SlideData} from '../../gestures/slide-gesture';
import {assign} from '../../util/util';
export class SwipeBackGesture extends SlideEdgeGesture {
public edges: Array<string>;
public threshold: string;
constructor(
element: HTMLElement,
opts: any = {},
element: HTMLElement,
options: any,
private _nav: NavController
) {
super(element, opts);
// Can check corners through use of eg 'left top'
this.edges = opts.edge.split(' ');
this.threshold = opts.threshold;
super(element, assign({
direction: 'x',
maxEdgeStart: 75
}, options));
}
onSlideStart() {
canStart(ev: any) {
// the gesture swipe angle must be mainly horizontal and the
// gesture distance would be relatively short for a swipe back
// and swipe back must be possible on this nav controller
if (ev.angle > -40 &&
ev.angle < 40 &&
ev.distance < 50 &&
this._nav.canSwipeBack()) {
// passed the tests, now see if the super says it's cool or not
return super.canStart(ev);
}
// nerp, not today
return false;
}
onSlideBeforeStart() {
console.debug('swipeBack, onSlideBeforeStart');
this._nav.swipeBackStart();
}
onSlide(slide, ev) {
this._nav.swipeBackProgress(slide.distance / slide.max);
onSlide(slide: SlideData) {
let stepValue = (slide.distance / slide.max);
console.debug('swipeBack, onSlide, distance', slide.distance, 'max', slide.max, 'stepValue', stepValue);
this._nav.swipeBackProgress(stepValue);
}
onSlideEnd(slide, ev) {
onSlideEnd(slide: SlideData, ev: any) {
let shouldComplete = (Math.abs(ev.velocityX) > 0.2 || Math.abs(slide.delta) > Math.abs(slide.max) * 0.5);
// TODO: calculate a better playback rate depending on velocity and distance
this._nav.swipeBackEnd(shouldComplete, 1);
let currentStepValue = (slide.distance / slide.max);
console.debug('swipeBack, onSlideEnd, shouldComplete', shouldComplete, 'currentStepValue', currentStepValue);
this._nav.swipeBackEnd(shouldComplete, currentStepValue);
}
}

View File

@ -291,7 +291,7 @@ export function run() {
});
});
describe('_transComplete', () => {
describe('_transFinish', () => {
it('should not entering/leaving state, after transition that isnt the most recent, and state already changed', () => {
let enteringView = new ViewController(Page1);
@ -301,7 +301,7 @@ export function run() {
nav._transIds = 2;
nav._transComplete(1, enteringView, leavingView);
nav._transFinish(1, enteringView, leavingView, 'forward', true);
expect(enteringView.state).toBe('somethingelse');
expect(leavingView.state).toBe('somethingelse');
@ -315,7 +315,7 @@ export function run() {
nav._transIds = 2;
nav._transComplete(1, enteringView, leavingView);
nav._transFinish(1, enteringView, leavingView, 'forward', true);
expect(enteringView.state).toBe(STATE_INACTIVE);
expect(leavingView.state).toBe(STATE_INACTIVE);
@ -329,12 +329,26 @@ export function run() {
nav._transIds = 1;
nav._transComplete(1, enteringView, leavingView);
nav._transFinish(1, enteringView, leavingView, 'forward', true);
expect(enteringView.state).toBe(STATE_ACTIVE);
expect(leavingView.state).toBe(STATE_INACTIVE);
});
it('should set entering inactive, leaving active, after transition has not completed', () => {
let enteringView = new ViewController(Page1);
enteringView.state = STATE_TRANS_ENTER;
let leavingView = new ViewController(Page2);
leavingView.state = STATE_TRANS_LEAVE;
nav._transIds = 1;
nav._transFinish(1, enteringView, leavingView, 'back', false);
expect(enteringView.state).toBe(STATE_INACTIVE);
expect(leavingView.state).toBe(STATE_ACTIVE);
});
});
describe('_insert', () => {
@ -541,7 +555,7 @@ export function run() {
});
it('should getByState()', () => {
expect(nav.getByState()).toBe(null);
expect(nav.getByState(null)).toBe(null);
let view1 = new ViewController(Page1);
view1.state = STATE_INIT_ENTER;
@ -618,7 +632,7 @@ export function run() {
});
// setup stuff
let nav;
let nav: NavController;
let config = new Config();
class Page1 {}

View File

@ -48,21 +48,23 @@ export class Gesture {
}
listen() {
this._hammer = Hammer(this.element, this._options);
if (!this.isListening) {
this._hammer = Hammer(this.element, this._options);
}
this.isListening = true;
}
unlisten() {
var type, i;
if (this._hammer) {
if (this._hammer && this.isListening) {
for (type in this._callbacks) {
for (i = 0; i < this._callbacks[type].length; i++) {
this._hammer.off(type, this._callbacks[type]);
}
}
this._callbacks = {};
this._hammer.destroy();
}
this._callbacks = {};
this.isListening = false;
}