mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-20 12:29:55 +08:00
swipe back refactor
This commit is contained in:
@ -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();
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
<template pane-anchor></template>
|
||||
<section class="content-container">
|
||||
<template content-anchor></template>
|
||||
<div class="swipe-handle"></div>
|
||||
</section>
|
||||
`,
|
||||
directives: [PaneAnchor, PaneContentAnchor, SwipeHandle]
|
||||
directives: [PaneAnchor, PaneContentAnchor]
|
||||
})
|
||||
export class Pane extends Ion {
|
||||
constructor(
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
29
ionic/components/view/swipe-back.ts
Normal file
29
ionic/components/view/swipe-back.ts
Normal file
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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,26 +337,62 @@ export class ViewController extends Ion {
|
||||
enteringItem.shouldCache = false;
|
||||
enteringItem.willEnter();
|
||||
|
||||
this.app.setTransitioning(true);
|
||||
|
||||
// wait for the new item to complete setup
|
||||
enteringItem.stage(() => {
|
||||
|
||||
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();
|
||||
// init the swipe back transition animation
|
||||
this._sbTrans = Transition.create(this, opts);
|
||||
this._sbTrans.easing('linear').progressStart();
|
||||
|
||||
let swipeBackPromise = new Promise(res => { this.sbResolve = res; });
|
||||
});
|
||||
});
|
||||
|
||||
swipeBackPromise.then((completeSwipeBack) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* @param {TODO} progress TODO
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {TODO} completeSwipeBack Should the swipe back complete or not.
|
||||
* @param {number} rate How fast it closes
|
||||
*/
|
||||
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, update each item's state
|
||||
// swipe back has completed navigating back
|
||||
// update each item's state
|
||||
enteringItem.state = ACTIVE_STATE;
|
||||
leavingItem.state = CACHED_STATE;
|
||||
|
||||
@ -366,12 +400,13 @@ export class ViewController extends Ion {
|
||||
leavingItem.didLeave();
|
||||
|
||||
if (this.router) {
|
||||
// notify router of the state change
|
||||
// notify router of the pop state change
|
||||
this.router.stateChange('pop', enteringItem);
|
||||
}
|
||||
|
||||
} else {
|
||||
// cancelled the swipe back, return items to original state
|
||||
// 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;
|
||||
|
||||
@ -382,45 +417,44 @@ export class ViewController extends Ion {
|
||||
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.transitionComplete();
|
||||
this._transComplete();
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)) );
|
||||
}
|
||||
_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;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* @param {TODO} completeSwipeBack TODO
|
||||
* @param {TODO} progress TODO
|
||||
* @param {TODO} playbackRate TODO
|
||||
*/
|
||||
swipeBackFinish(completeSwipeBack, playbackRate) {
|
||||
// to reverse the animation use a negative playbackRate
|
||||
if (this.sbTransition && this.sbActive) {
|
||||
this.sbActive = false;
|
||||
let opts = {
|
||||
edge: 'left',
|
||||
threshold: this.sbThreshold
|
||||
};
|
||||
this.sbGesture = new SwipeBackGesture(this.getNativeElement(), opts, this);
|
||||
console.debug('SwipeBackGesture listen');
|
||||
this.sbGesture.listen();
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
} 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) {
|
||||
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,32 +516,35 @@ 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;
|
||||
}
|
||||
// IonicApp global isEnabled, maybe something else has the app disabled
|
||||
return this.app.isEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
|
@ -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');
|
||||
|
@ -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
|
||||
// --------------------------------------------------
|
||||
|
||||
|
Reference in New Issue
Block a user