diff --git a/ionic/animations/animation.ts b/ionic/animations/animation.ts index 07c761efd4..79268caf99 100644 --- a/ionic/animations/animation.ts +++ b/ionic/animations/animation.ts @@ -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 = []; } diff --git a/ionic/components/nav/nav-controller.ts b/ionic/components/nav/nav-controller.ts index d72f496863..65a7f818bf 100644 --- a/ionic/components/nav/nav-controller.ts +++ b/ionic/components/nav/nav-controller.ts @@ -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 = []; @@ -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 { 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'; diff --git a/ionic/components/nav/nav.ts b/ionic/components/nav/nav.ts index 0d9f3727e8..1a11bf757b 100644 --- a/ionic/components/nav/nav.ts +++ b/ionic/components/nav/nav.ts @@ -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'; * * * - * @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' ); } } diff --git a/ionic/components/nav/swipe-back.ts b/ionic/components/nav/swipe-back.ts index 9c90494ef1..ed26de85b4 100644 --- a/ionic/components/nav/swipe-back.ts +++ b/ionic/components/nav/swipe-back.ts @@ -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; - 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); } } diff --git a/ionic/components/nav/test/nav-controller.spec.ts b/ionic/components/nav/test/nav-controller.spec.ts index 651b4e0264..db99a97c7d 100644 --- a/ionic/components/nav/test/nav-controller.spec.ts +++ b/ionic/components/nav/test/nav-controller.spec.ts @@ -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 {} diff --git a/ionic/gestures/gesture.ts b/ionic/gestures/gesture.ts index 2b43555059..fe4f776195 100644 --- a/ionic/gestures/gesture.ts +++ b/ionic/gestures/gesture.ts @@ -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; }