diff --git a/ionic/animations/animation.ts b/ionic/animations/animation.ts index 88e7a1b32c..a3f90d465d 100644 --- a/ionic/animations/animation.ts +++ b/ionic/animations/animation.ts @@ -392,6 +392,7 @@ export class Animation { } progress(value) { + value = Math.min(1, Math.max(0, value)); this.isProgress = true; let i; @@ -589,7 +590,6 @@ class Animate { if (animation) { // passed a number between 0 and 1 - value = Math.max(0, Math.min(1, value)); if (animation.playState !== 'paused') { animation.pause(); diff --git a/ionic/components/app/activator.ts b/ionic/components/app/activator.ts index 5687b3973b..ae76edd35c 100644 --- a/ionic/components/app/activator.ts +++ b/ionic/components/app/activator.ts @@ -84,7 +84,7 @@ export class Activator { touchEnd(ev) { let self = this; - if (self.tapPolyfill && self.start && !self.app.isTransitioning()) { + if (self.tapPolyfill && self.start && self.app.isEnabled()) { let endCoord = pointerCoord(ev); if (!hasPointerMoved(self.pointerTolerance, self.start, endCoord)) { @@ -140,7 +140,7 @@ export class Activator { pointerStart(ev) { let targetEle = this.getActivatableTarget(ev.target); - if (targetEle && !this.app.isTransitioning()) { + if (targetEle && this.app.isEnabled()) { this.start = pointerCoord(ev); this.queueActivate(targetEle); @@ -179,7 +179,7 @@ export class Activator { * @return {boolean} True if click event should be allowed, otherwise false. */ allowClick(ev) { - if (this.app.isTransitioning()) { + if (!this.app.isEnabled()) { return false; } if (!ev.isIonicTap) { @@ -257,11 +257,10 @@ export class Activator { deactivate() { const self = this; - if (self.app.isTransitioning() && self.deactivateAttempt < 30) { - // the app is actively transitioning, don't bother deactivating - // anything this makes it easier on the GPU so it doesn't - // have to redraw any buttons during a transition - // retry + if (!self.app.isEnabled() && self.deactivateAttempt < 30) { + // the app is actively disabled, so don't bother deactivating anything. + // this makes it easier on the GPU so it doesn't have to redraw any + // buttons during a transition. This will retry in XX milliseconds. ++self.deactivateAttempt; self.queueDeactivate(); diff --git a/ionic/components/app/app.ts b/ionic/components/app/app.ts index be3847c64d..87a8f6c12f 100644 --- a/ionic/components/app/app.ts +++ b/ionic/components/app/app.ts @@ -3,6 +3,7 @@ import {ROUTER_BINDINGS, HashLocationStrategy, LocationStrategy, Router} from 'a import {IonicConfig} from '../../config/config'; import {IonicPlatform, Platform} from '../../platform/platform'; +import {ClickBlock} from '../../util/click-block'; import * as util from '../../util/util'; // injectables @@ -40,7 +41,7 @@ export class IonicApp { */ constructor() { this.overlays = []; - this._transDone = 0; + this._enableTime = 0; // Our component registry map this.components = {}; @@ -77,21 +78,27 @@ export class IonicApp { } /** - * Sets if the app is currently transitioning or not. For example - * this is set to `true` while views transition, a modal slides up, an action-menu - * slides up, etc. After the transition completes it is set back to `false`. - * @param {bool} isTransitioning + * Sets if the app is currently enabled or not, meaning if it's + * available to accept new user commands. For example, this is set to `false` + * while views transition, a modal slides up, an action-menu + * slides up, etc. After the transition completes it is set back to `true`. + * @param {bool} isEnabled + * @param {bool} fallback When `isEnabled` is set to `false`, this argument + * is used to set the maximum number of milliseconds that app will wait until + * it will automatically enable the app again. It's basically a fallback incase + * something goes wrong during a transition and the app wasn't re-enabled correctly. */ - setTransitioning(isTransitioning, msTilDone=800) { - this._transDone = (isTransitioning ? Date.now() + msTilDone : 0); + setEnabled(isEnabled, fallback=700) { + this._enableTime = (isEnabled ? 0 : Date.now() + fallback); + ClickBlock(!isEnabled, fallback + 100); } /** * Boolean if the app is actively transitioning or not. * @return {bool} */ - isTransitioning() { - return (this._transDone > Date.now()); + isEnabled() { + return (this._enableTime < Date.now()); } /** diff --git a/ionic/components/app/structure.scss b/ionic/components/app/structure.scss index d2e5a6888e..f0420772a3 100644 --- a/ionic/components/app/structure.scss +++ b/ionic/components/app/structure.scss @@ -13,7 +13,6 @@ $z-index-navbar-container: 10 !default; $z-index-content: 5 !default; $z-index-toolbar: 10 !default; -$z-index-swipe-handle: 15 !default; $z-index-toolbar-border: 20 !default; $z-index-list-border: 50 !default; diff --git a/ionic/components/menu/menu.ts b/ionic/components/menu/menu.ts index 296e74a8e8..e15fb636fa 100644 --- a/ionic/components/menu/menu.ts +++ b/ionic/components/menu/menu.ts @@ -129,6 +129,7 @@ export class Menu extends Ion { setProgess(value) { // user actively dragging the menu this._disable(); + this.app.setEnabled(false); this._type.setProgess(value); } @@ -147,7 +148,7 @@ export class Menu extends Ion { this.getBackdropElement().classList.add('show-backdrop'); this._disable(); - this.app.setTransitioning(true); + this.app.setEnabled(false); } _after(isOpen) { @@ -165,7 +166,7 @@ export class Menu extends Ion { this.getBackdropElement().classList.remove('show-backdrop'); } - this.app.setTransitioning(false); + this.app.setEnabled(true); } _disable() { @@ -232,6 +233,7 @@ export class Menu extends Ion { onDestroy() { this.app.unregister(this.id); + this._gesture && this._gesture.destroy(); this._type && this._type.onDestroy(); this.contentElement = null; } diff --git a/ionic/components/nav/pane.ts b/ionic/components/nav/pane.ts index 3f7990c81d..6181fedc2b 100644 --- a/ionic/components/nav/pane.ts +++ b/ionic/components/nav/pane.ts @@ -3,7 +3,6 @@ import {Component, Directive, View, ElementRef, Inject, forwardRef, Injector, bi import {Ion} from '../ion'; import {IonicConfig} from '../../config/config'; import {ViewController} from '../view/view-controller'; -import {SwipeHandle} from './swipe-handle'; import {IonicComponent} from '../../config/annotations'; import {PaneAnchor, PaneContentAnchor, NavBarContainer} from './anchors'; @@ -116,10 +115,9 @@ export class PaneController {
-
`, - directives: [PaneAnchor, PaneContentAnchor, SwipeHandle] + directives: [PaneAnchor, PaneContentAnchor] }) export class Pane extends Ion { constructor( diff --git a/ionic/components/nav/swipe-handle.ts b/ionic/components/nav/swipe-handle.ts deleted file mode 100644 index d617072450..0000000000 --- a/ionic/components/nav/swipe-handle.ts +++ /dev/null @@ -1,119 +0,0 @@ -import {ElementRef, Directive, Host, Optional, Inject, forwardRef, NgZone} from 'angular2/angular2'; - -import {ViewController} from '../view/view-controller'; -import {Pane} from './pane'; -import {Gesture} from 'ionic/gestures/gesture'; - - -/** - * TODO - */ -@Directive({ - selector: '.swipe-handle', - host: { - '[class.show-handle]': 'showHandle' - } -}) -export class SwipeHandle { - /** - * TODO - * @param {ViewController=} viewCtrl TODO - * @param {Pane} pane TODO - * @param {ElementRef} elementRef TODO - * @param {NgZone} ngZone TODO - */ - constructor( - @Optional() @Inject(forwardRef(() => ViewController)) viewCtrl: ViewController, - @Host() @Inject(forwardRef(() => Pane)) pane: Pane, - elementRef: ElementRef, - ngZone: NgZone - ) { - - if (!viewCtrl || !viewCtrl.isSwipeBackEnabled() || !pane) return; - - const self = this; - - self.pane = pane; - self.viewCtrl = viewCtrl; - self.zone = ngZone; - - this.zone.runOutsideAngular(() => { - let gesture = self.gesture = new Gesture(elementRef.nativeElement); - gesture.listen(); - - function dragHorizontal(ev) { - self.onDragHorizontal(ev); - } - - gesture.on('panend', gesture => { self.onDragEnd(gesture); }); - gesture.on('panleft', dragHorizontal); - gesture.on('panright', dragHorizontal); - }); - - self.startX = null; - self.width = null; - } - - onDragEnd(gesture) { - gesture.srcEvent.preventDefault(); - gesture.srcEvent.stopPropagation(); - - // TODO: POLISH THESE NUMBERS WITH GOOD MATHIFICATION - let progress = (gesture.center.x - this.startX) / this.width; - let completeSwipeBack = (progress > 0.5); - let playbackRate = 4; - - if (completeSwipeBack) { - // complete swipe back - if (progress > 0.9) { - playbackRate = 1; - } else if (progress > 0.8) { - playbackRate = 2; - } else if (progress > 0.7) { - playbackRate = 3; - } - - } else { - // cancel swipe back - if (progress < 0.1) { - playbackRate = 1; - } else if (progress < 0.2) { - playbackRate = 2; - } else if (progress < 0.3) { - playbackRate = 3; - } - } - - this.zone.run(() => { - this.viewCtrl.swipeBackFinish(completeSwipeBack, playbackRate); - }); - - this.startX = null; - } - - onDragHorizontal(gesture) { - this.zone.run(() => { - if (this.startX === null) { - // starting drag - gesture.srcEvent.preventDefault(); - gesture.srcEvent.stopPropagation(); - - this.startX = gesture.center.x; - this.width = this.pane.width() - this.startX; - - this.viewCtrl.swipeBackStart(); - } - - this.viewCtrl.swipeBackProgress( (gesture.center.x - this.startX) / this.width ); - }); - } - - get showHandle() { - return (this.viewCtrl ? this.viewCtrl.canSwipeBack() : false); - } - - onDestroy() { - this.gesture && this.gesture.destroy(); - } - -} diff --git a/ionic/components/overlay/overlay.ts b/ionic/components/overlay/overlay.ts index 0aa164ee9f..7c359de0be 100644 --- a/ionic/components/overlay/overlay.ts +++ b/ionic/components/overlay/overlay.ts @@ -3,7 +3,6 @@ import {DirectiveBinding} from 'angular2/src/core/compiler/element_injector'; import {IonicApp} from '../app/app'; import {Animation} from '../../animations/animation'; -import {ClickBlock} from '../../util/click-block'; import * as util from 'ionic/util'; @@ -127,16 +126,14 @@ export class OverlayRef { animation.before.addClass('show-overlay'); - ClickBlock(true, animation.duration() + 200); - this.app.setTransitioning(true); + this.app.setEnabled(false, animation.duration()); this.app.zoneRunOutside(() => { animation.play().then(() => { this.app.zoneRun(() => { - ClickBlock(false); - this.app.setTransitioning(false); + this.app.setEnabled(true); animation.dispose(); instance.viewDidEnter && instance.viewDidEnter(); resolve(); @@ -161,8 +158,7 @@ export class OverlayRef { let animation = Animation.create(this._elementRef.nativeElement, animationName); animation.after.removeClass('show-overlay'); - ClickBlock(true, animation.duration() + 200); - this.app.setTransitioning(true, animation.duration() + 200); + this.app.setEnabled(false, animation.duration()); animation.play().then(() => { instance.viewDidLeave && instance.viewDidLeave(); @@ -170,8 +166,7 @@ export class OverlayRef { this._dispose(); - ClickBlock(false); - this.app.setTransitioning(false); + this.app.setEnabled(true); animation.dispose(); resolve(); diff --git a/ionic/components/tabs/tabs.ts b/ionic/components/tabs/tabs.ts index 5bd5f62acf..bd4d3eb51d 100644 --- a/ionic/components/tabs/tabs.ts +++ b/ionic/components/tabs/tabs.ts @@ -1,5 +1,6 @@ import {Component, Directive, View, Injector, NgFor, ElementRef, Optional, Host, forwardRef, NgZone} from 'angular2/angular2'; +import {IonicApp} from '../app/app'; import {ViewController} from '../view/view-controller'; import {ViewItem} from '../view/view-item'; import {Icon} from '../icon/icon'; @@ -56,11 +57,13 @@ export class Tabs extends ViewController { constructor( @Optional() hostViewCtrl: ViewController, @Optional() viewItem: ViewItem, + app: IonicApp, injector: Injector, elementRef: ElementRef, zone: NgZone ) { super(hostViewCtrl, injector, elementRef, zone); + this.app = app; // Tabs may also be an actual ViewItem which was navigated to // if Tabs is static and not navigated to within a ViewController @@ -117,7 +120,7 @@ export class Tabs extends ViewController { enteringItem = this.getByInstance(tab) } - if (!enteringItem || !enteringItem.instance || this.isTransitioning()) { + if (!enteringItem || !enteringItem.instance || !this.app.isEnabled()) { return Promise.reject(); } diff --git a/ionic/components/text-input/text-input.ts b/ionic/components/text-input/text-input.ts index d88d911d79..020922d62e 100644 --- a/ionic/components/text-input/text-input.ts +++ b/ionic/components/text-input/text-input.ts @@ -7,7 +7,6 @@ import {Label} from './label'; import {Ion} from '../ion'; import {IonicApp} from '../app/app'; import {Content} from '../content/content'; -import {ClickBlock} from '../../util/click-block'; import * as dom from '../../util/dom'; import {IonicPlatform} from '../../platform/platform'; @@ -160,7 +159,7 @@ export class TextInput extends Ion { * @param {Event} ev TODO */ pointerStart(ev) { - if (this.scrollAssist && !this.app.isTransitioning()) { + if (this.scrollAssist && this.app.isEnabled()) { // remember where the touchstart/mousedown started this.startCoord = dom.pointerCoord(ev); } @@ -171,8 +170,7 @@ export class TextInput extends Ion { * @param {Event} ev TODO */ pointerEnd(ev) { - - if (this.app.isTransitioning()) { + if (!this.app.isEnabled()) { ev.preventDefault(); ev.stopPropagation(); @@ -234,8 +232,7 @@ export class TextInput extends Ion { // manually scroll the text input to the top // do not allow any clicks while it's scrolling - ClickBlock(true, SCROLL_INTO_VIEW_DURATION + 100); - this.app.setTransitioning(true, SCROLL_INTO_VIEW_DURATION + 100); + this.app.setEnabled(false, SCROLL_INTO_VIEW_DURATION); // temporarily move the focus to the focus holder so the browser // doesn't freak out while it's trying to get the input in place @@ -249,8 +246,7 @@ export class TextInput extends Ion { this.setFocus(); // all good, allow clicks again - ClickBlock(false); - this.app.setTransitioning(false); + this.app.setEnabled(true); }); } else { diff --git a/ionic/components/toolbar/toolbar.ts b/ionic/components/toolbar/toolbar.ts index dca04bff82..4aacc02b02 100644 --- a/ionic/components/toolbar/toolbar.ts +++ b/ionic/components/toolbar/toolbar.ts @@ -34,7 +34,7 @@ export class ToolbarBase extends Ion { * @returns {TODO} TODO */ getTitleRef() { - return this.titleCmp && this.titleCmp.elementRef.textContent; + return this.titleCmp && this.titleCmp.elementRef; } /** diff --git a/ionic/components/view/swipe-back.ts b/ionic/components/view/swipe-back.ts new file mode 100644 index 0000000000..a4c5372708 --- /dev/null +++ b/ionic/components/view/swipe-back.ts @@ -0,0 +1,29 @@ +import {SlideEdgeGesture} from 'ionic/gestures/slide-edge-gesture'; + + +export class SwipeBackGesture extends SlideEdgeGesture { + + constructor(element: Element, opts: Object = {}, viewCtrl) { + super(element, opts); + // Can check corners through use of eg 'left top' + this.edges = opts.edge.split(' '); + this.threshold = opts.threshold; + this.viewCtrl = viewCtrl; + } + + onSlideStart() { + this.viewCtrl.swipeBackStart(); + } + + onSlide(slide, ev) { + this.viewCtrl.swipeBackProgress(slide.distance / slide.max); + } + + onSlideEnd(slide, ev) { + 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.viewCtrl.swipeBackFinish(shouldComplete, 1); + } + +} diff --git a/ionic/components/view/view-controller.ts b/ionic/components/view/view-controller.ts index 206201e261..3fc5f4bfdf 100644 --- a/ionic/components/view/view-controller.ts +++ b/ionic/components/view/view-controller.ts @@ -9,8 +9,7 @@ import {ViewItem} from './view-item'; import {NavController} from '../nav/nav-controller'; import {PaneController} from '../nav/pane'; import {Transition} from '../../transitions/transition'; -import {ClickBlock} from '../../util/click-block'; -import {SlideEdgeGesture} from 'ionic/gestures/slide-edge-gesture'; +import {SwipeBackGesture} from './swipe-back'; import * as util from 'ionic/util'; /** @@ -39,9 +38,9 @@ export class ViewController extends Ion { this.items = []; this.panes = new PaneController(this); - this.sbTransition = null; - this.sbActive = false; - this.sbEnabled = true; + this._sbTrans = null; + this.sbEnabled = config.setting('swipeBackEnabled') || false; + this.sbThreshold = config.setting('swipeBackThreshold') || 40 this.id = ++ctrlIds; this._ids = -1; @@ -62,7 +61,7 @@ export class ViewController extends Ion { * @returns {Promise} TODO */ push(componentType, params = {}, opts = {}) { - if (!componentType || this.isTransitioning()) { + if (!componentType || !this._isEnabled()) { return Promise.reject(); } @@ -110,7 +109,7 @@ export class ViewController extends Ion { * @returns {Promise} TODO */ pop(opts = {}) { - if (this.isTransitioning() || this.items.length < 2) { + if (!this._isEnabled() || !this.canGoBack()) { return Promise.reject(); } @@ -146,7 +145,7 @@ export class ViewController extends Ion { }); } else { - this.transitionComplete(); + this._transComplete(); resolve(); } @@ -279,8 +278,7 @@ export class ViewController extends Ion { if (duration > 64) { // block any clicks during the transition and provide a // fallback to remove the clickblock if something goes wrong - ClickBlock(true, duration + 200); - this.app.setTransitioning(true, duration + 200); + this._setEnabled(false, duration); } // start the transition @@ -298,7 +296,7 @@ export class ViewController extends Ion { // all done! this.zone.run(() => { - this.transitionComplete(); + this._transComplete(); callback(); }); }); @@ -313,12 +311,12 @@ export class ViewController extends Ion { * TODO */ swipeBackStart() { - if (this.isTransitioning() || this.items.length < 2) { + if (!this._isEnabled() || !this.canSwipeBack()) { return; } - this.sbActive = true; - this.sbResolve = null; + // disables the app during the transition + this._setEnabled(false); // default the direction to "back" let opts = { @@ -339,55 +337,20 @@ export class ViewController extends Ion { enteringItem.shouldCache = false; enteringItem.willEnter(); - this.app.setTransitioning(true); - // wait for the new item to complete setup enteringItem.stage(() => { - // set that the new item pushed on the stack is staged to be entering/leaving - // staged state is important for the transition to find the correct item - enteringItem.state = STAGED_ENTERING_STATE; - leavingItem.state = STAGED_LEAVING_STATE; + this.zone.runOutsideAngular(() => { + // set that the new item pushed on the stack is staged to be entering/leaving + // staged state is important for the transition to find the correct item + enteringItem.state = STAGED_ENTERING_STATE; + leavingItem.state = STAGED_LEAVING_STATE; - // init the transition animation - this.sbTransition = Transition.create(this, opts); - this.sbTransition.easing('linear').progressStart(); - - let swipeBackPromise = new Promise(res => { this.sbResolve = res; }); - - swipeBackPromise.then((completeSwipeBack) => { - - if (completeSwipeBack) { - // swipe back has completed, update each item's state - enteringItem.state = ACTIVE_STATE; - leavingItem.state = CACHED_STATE; - - enteringItem.didEnter(); - leavingItem.didLeave(); - - if (this.router) { - // notify router of the state change - this.router.stateChange('pop', enteringItem); - } - - } else { - // cancelled the swipe back, return items to original state - leavingItem.state = ACTIVE_STATE; - enteringItem.state = CACHED_STATE; - - leavingItem.willEnter(); - leavingItem.didEnter(); - enteringItem.didLeave(); - - leavingItem.shouldDestroy = false; - enteringItem.shouldDestroy = false; - } - - // all done! - this.transitionComplete(); + // init the swipe back transition animation + this._sbTrans = Transition.create(this, opts); + this._sbTrans.easing('linear').progressStart(); }); - }); } @@ -396,31 +359,102 @@ export class ViewController extends Ion { * TODO * @param {TODO} progress TODO */ - swipeBackProgress(progress) { - if (this.sbTransition) { - ClickBlock(true, 4000); - this.app.setTransitioning(true, 4000); - this.sbTransition.progress( Math.min(1, Math.max(0, progress)) ); + swipeBackProgress(value) { + if (this._sbTrans) { + // continue to disable the app while actively dragging + this._setEnabled(false, 4000); + + // set the transition animation's progress + this._sbTrans.progress(value); } } /** - * TODO - * @param {TODO} completeSwipeBack TODO - * @param {TODO} progress TODO - * @param {TODO} playbackRate TODO + * @private + * @param {TODO} completeSwipeBack Should the swipe back complete or not. + * @param {number} rate How fast it closes */ - swipeBackFinish(completeSwipeBack, playbackRate) { - // to reverse the animation use a negative playbackRate - if (this.sbTransition && this.sbActive) { - this.sbActive = false; + swipeBackFinish(completeSwipeBack, rate) { + if (!this._sbTrans) return; + + // disables the app during the transition + this._setEnabled(false); + + this._sbTrans.progressFinish(completeSwipeBack, rate).then(() => { + + this.zone.run(() => { + // find the items that were entering and leaving + let enteringItem = this.getStagedEnteringItem(); + let leavingItem = this.getStagedLeavingItem(); + + if (enteringItem && leavingItem) { + // finish up the animation + + if (completeSwipeBack) { + // swipe back has completed navigating back + // update each item's state + enteringItem.state = ACTIVE_STATE; + leavingItem.state = CACHED_STATE; + + enteringItem.didEnter(); + leavingItem.didLeave(); + + if (this.router) { + // notify router of the pop state change + this.router.stateChange('pop', enteringItem); + } + + } else { + // cancelled the swipe back, they didn't end up going back + // return items to their original state + leavingItem.state = ACTIVE_STATE; + enteringItem.state = CACHED_STATE; + + leavingItem.willEnter(); + leavingItem.didEnter(); + enteringItem.didLeave(); + + leavingItem.shouldDestroy = false; + enteringItem.shouldDestroy = false; + } + } + + // empty out and dispose the swipe back transition animation + this._sbTrans && this._sbTrans.dispose(); + this._sbTrans = null; + + // all done! + this._transComplete(); - this.sbTransition.progressFinish(completeSwipeBack, playbackRate).then(() => { - this.sbResolve && this.sbResolve(completeSwipeBack); - this.sbTransition && this.sbTransition.dispose(); - this.sbResolve = this.sbTransition = null; - this.app.setTransitioning(false); }); + }); + + } + + _runSwipeBack() { + if (this.canSwipeBack()) { + // it is possible to swipe back + + if (this.sbGesture) { + // this is already an active gesture, don't create another one + return; + } + + let opts = { + edge: 'left', + threshold: this.sbThreshold + }; + this.sbGesture = new SwipeBackGesture(this.getNativeElement(), opts, this); + console.debug('SwipeBackGesture 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; } } @@ -437,27 +471,33 @@ export class ViewController extends Ion { } /** - * TODO - * @returns {TODO} TODO + * 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. + * If it is possible to go back, and swipe back is enabled, then this + * will return true. + * @returns {boolean} */ canSwipeBack() { - if (this.sbEnabled) { - let activeItem = this.getActive(); - if (activeItem) { - return activeItem.enableBack(); - } + return (this.sbEnabled && this.canGoBack()); + } + + /** + * Returns `true` if there's a valid previous view that we can pop back to. + * Otherwise returns false. + * @returns {boolean} + */ + canGoBack() { + let activeItem = this.getActive(); + if (activeItem) { + return activeItem.enableBack(); } return false; } - runSwipeBack() { - if (!this.canSwipeBack()) return; - } - /** - * TODO + * @private */ - transitionComplete() { + _transComplete() { let destroys = []; this.items.forEach(item => { @@ -476,31 +516,34 @@ export class ViewController extends Ion { item.destroy(); }); - // allow clicks again - ClickBlock(false); - this.app.setTransitioning(false); + // allow clicks again, but still set an enable time + // meaning nothing with this view controller can happen for XXms + this._setEnabled(true); if (this.items.length === 1) { this.elementRef.nativeElement.classList.add('has-views'); } - this.runSwipeBack(); + this._runSwipeBack(); } - /** - * TODO - * @returns {boolean} TODO - */ - isTransitioning() { - let state; - for (let i = 0, ii = this.items.length; i < ii; i++) { - state = this.items[i].state; - if (state === STAGED_ENTERING_STATE || - state === STAGED_LEAVING_STATE) { - return true; - } + _setEnabled(isEnabled, fallback) { + // used to prevent unwanted transitions after JUST completing one + // prevents the user from crazy clicking everything and possible flickers + // gives the app some time to cool off, slow down, and think about life + this._enableTime = Date.now() + 100; + + // IonicApp global setEnabled to prevent other things from starting up + this.app.setEnabled(isEnabled, fallback); + } + + _isEnabled() { + // used to prevent unwanted transitions after JUST completing one + if (this._enableTime > Date.now()) { + return false; } - return false; + // IonicApp global isEnabled, maybe something else has the app disabled + return this.app.isEnabled(); } /** diff --git a/ionic/platform/registry.ts b/ionic/platform/registry.ts index 9df4180097..8b3e8f2301 100644 --- a/ionic/platform/registry.ts +++ b/ionic/platform/registry.ts @@ -79,6 +79,11 @@ IonicPlatform.register({ }, keyboardHeight: 290, hoverCSS: false, + swipeBackEnabled: function(p) { + return true; // TODO: remove me! Force it to always work for iOS mode for now + return /iphone|ipad|ipod/i.test(p.navigatorPlatform()); + }, + swipeBackThreshold: 40, }, isMatch(p) { return p.isPlatform('ios', 'iphone|ipad|ipod'); diff --git a/ionic/util/util.scss b/ionic/util/util.scss index 0aab7f0c26..fc3abce1cd 100755 --- a/ionic/util/util.scss +++ b/ionic/util/util.scss @@ -97,31 +97,6 @@ backdrop { } -// Swipe Handle -// -------------------------------------------------- - -$swipe-handle-width: 20px !default; -$swipe-handle-top: 70px !default; -$swipe-handle-bottom: 70px !default; - -.swipe-handle { - position: absolute; - top: $swipe-handle-top; - left: 0; - bottom: $swipe-handle-bottom; - width: $swipe-handle-width; - z-index: $z-index-swipe-handle; - - //background: red; - //opacity: 0.2; - - transform: translate3d(-999px, 0px, 0px); - &.show-handle { - transform: translate3d(0px, 0px, 0px); - } -} - - // Loading Icon // --------------------------------------------------