MOBILE-2738 tabs: Control navigation when backbutton is actioned
parent
ac9bd47da0
commit
d35f0e2ffc
|
@ -12,10 +12,12 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, Optional, ElementRef, Renderer, ViewEncapsulation, forwardRef, ViewChild, Input } from '@angular/core';
|
||||
import { Component, Optional, ElementRef, Renderer, ViewEncapsulation, forwardRef, ViewChild, Input,
|
||||
OnDestroy } from '@angular/core';
|
||||
import { Tabs, NavController, ViewController, App, Config, Platform, DeepLinker, Keyboard, RootNode } from 'ionic-angular';
|
||||
import { CoreIonTabComponent } from './ion-tab';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
|
||||
/**
|
||||
* Equivalent to ion-tabs. It has 2 improvements:
|
||||
|
@ -28,7 +30,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils';
|
|||
encapsulation: ViewEncapsulation.None,
|
||||
providers: [{provide: RootNode, useExisting: forwardRef(() => CoreIonTabsComponent) }]
|
||||
})
|
||||
export class CoreIonTabsComponent extends Tabs {
|
||||
export class CoreIonTabsComponent extends Tabs implements OnDestroy {
|
||||
|
||||
/**
|
||||
* Whether the tabs have been loaded. If defined, tabs won't be initialized until it's set to true.
|
||||
|
@ -62,9 +64,12 @@ export class CoreIonTabsComponent extends Tabs {
|
|||
protected viewInit = false; // Whether the view has been initialized.
|
||||
protected initialized = false; // Whether tabs have been initialized.
|
||||
|
||||
constructor(protected utils: CoreUtilsProvider, @Optional() parent: NavController, @Optional() viewCtrl: ViewController,
|
||||
_app: App, config: Config, elementRef: ElementRef, _plt: Platform, renderer: Renderer, _linker: DeepLinker,
|
||||
keyboard?: Keyboard) {
|
||||
protected firstSelectedTab: string;
|
||||
protected unregisterBackButtonAction: any;
|
||||
|
||||
constructor(protected utils: CoreUtilsProvider, protected appProvider: CoreAppProvider, @Optional() parent: NavController,
|
||||
@Optional() viewCtrl: ViewController, _app: App, config: Config, elementRef: ElementRef, _plt: Platform,
|
||||
renderer: Renderer, _linker: DeepLinker, keyboard?: Keyboard) {
|
||||
super(parent, viewCtrl, _app, config, elementRef, _plt, renderer, _linker, keyboard);
|
||||
}
|
||||
|
||||
|
@ -75,6 +80,8 @@ export class CoreIonTabsComponent extends Tabs {
|
|||
this.viewInit = true;
|
||||
|
||||
super.ngAfterViewInit();
|
||||
|
||||
this.registerBackButtonAction();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -146,6 +153,8 @@ export class CoreIonTabsComponent extends Tabs {
|
|||
this.select(tab);
|
||||
}
|
||||
}
|
||||
|
||||
this.firstSelectedTab = this._selectHistory[0] || null;
|
||||
});
|
||||
} else {
|
||||
// Tabs not loaded yet. Set the tab bar position so the tab bar is shown, it'll have a spinner.
|
||||
|
@ -155,6 +164,42 @@ export class CoreIonTabsComponent extends Tabs {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register back button action.
|
||||
*/
|
||||
protected registerBackButtonAction(): void {
|
||||
this.unregisterBackButtonAction = this.appProvider.registerBackButtonAction(() => {
|
||||
let tab = this.previousTab(true);
|
||||
if (tab) {
|
||||
const selectedTab = this.getSelected();
|
||||
|
||||
// Remove curent and previous tabs from history.
|
||||
this._selectHistory = this._selectHistory.filter((tabId) => {
|
||||
return selectedTab.id != tabId && tab.id != tabId;
|
||||
});
|
||||
|
||||
this.select(tab);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
const selected = this.getSelected();
|
||||
if (selected && this.firstSelectedTab && selected.id != this.firstSelectedTab) {
|
||||
// All history is gone but we are not in the first selected tab.
|
||||
this._selectHistory = [];
|
||||
|
||||
tab = this._tabs.find((t) => { return t.id === this.firstSelectedTab; });
|
||||
if (tab && tab.enabled && tab.show) {
|
||||
this.select(tab);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}, 250);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a tab from the list of tabs.
|
||||
*
|
||||
|
@ -204,4 +249,12 @@ export class CoreIonTabsComponent extends Tabs {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
// Unregister the custom back button action for this page
|
||||
this.unregisterBackButtonAction && this.unregisterBackButtonAction();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,9 +16,10 @@ import {
|
|||
Component, Input, Output, EventEmitter, OnInit, OnChanges, OnDestroy, AfterViewInit, ViewChild, ElementRef,
|
||||
SimpleChange
|
||||
} from '@angular/core';
|
||||
import { CoreTabComponent } from './tab';
|
||||
import { Content, Slides } from 'ionic-angular';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreTabComponent } from './tab';
|
||||
|
||||
/**
|
||||
* This component displays some tabs that usually share data between them.
|
||||
|
@ -72,8 +73,13 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe
|
|||
protected isCurrentView = true;
|
||||
protected shouldSlideToInitial = false; // Whether we need to slide to the initial slide because it's out of view.
|
||||
protected hasSliddenToInitial = false; // Whether we've already slidden to the initial slide or there was no need.
|
||||
protected selectHistory = [];
|
||||
|
||||
constructor(element: ElementRef, protected content: Content, protected domUtils: CoreDomUtilsProvider) {
|
||||
protected firstSelectedTab: number;
|
||||
protected unregisterBackButtonAction: any;
|
||||
|
||||
constructor(element: ElementRef, protected content: Content, protected domUtils: CoreDomUtilsProvider,
|
||||
protected appProvider: CoreAppProvider) {
|
||||
this.tabBarElement = element.nativeElement;
|
||||
}
|
||||
|
||||
|
@ -128,12 +134,47 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe
|
|||
if (this.initialized) {
|
||||
this.calculateSlides();
|
||||
}
|
||||
|
||||
this.registerBackButtonAction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register back button action.
|
||||
*/
|
||||
protected registerBackButtonAction(): void {
|
||||
this.unregisterBackButtonAction = this.appProvider.registerBackButtonAction(() => {
|
||||
// The previous page in history is not the last one, we need the previous one.
|
||||
if (this.selectHistory.length > 1) {
|
||||
const tab = this.selectHistory[this.selectHistory.length - 2];
|
||||
|
||||
// Remove curent and previous tabs from history.
|
||||
this.selectHistory = this.selectHistory.filter((tabId) => {
|
||||
return this.selected != tabId && tab != tabId;
|
||||
});
|
||||
|
||||
this.selectTab(tab);
|
||||
|
||||
return true;
|
||||
} else if (this.selected != this.firstSelectedTab) {
|
||||
// All history is gone but we are not in the first selected tab.
|
||||
this.selectHistory = [];
|
||||
|
||||
this.selectTab(this.firstSelectedTab);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}, 750);
|
||||
}
|
||||
|
||||
/**
|
||||
* User left the page that contains the component.
|
||||
*/
|
||||
ionViewDidLeave(): void {
|
||||
// Unregister the custom back button action for this page
|
||||
this.unregisterBackButtonAction && this.unregisterBackButtonAction();
|
||||
|
||||
this.isCurrentView = false;
|
||||
}
|
||||
|
||||
|
@ -229,6 +270,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe
|
|||
}
|
||||
|
||||
if (selectedTab) {
|
||||
this.firstSelectedTab = selectedIndex;
|
||||
this.selectTab(selectedIndex);
|
||||
}
|
||||
|
||||
|
@ -405,6 +447,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe
|
|||
this.slides.slideTo(index);
|
||||
}
|
||||
|
||||
this.selectHistory.push(index);
|
||||
this.selected = index;
|
||||
newTab.selectTab();
|
||||
this.ionChange.emit(newTab);
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable, NgZone } from '@angular/core';
|
||||
import { Platform, App, NavController } from 'ionic-angular';
|
||||
import { Platform, App, NavController, MenuController } from 'ionic-angular';
|
||||
import { Keyboard } from '@ionic-native/keyboard';
|
||||
import { Network } from '@ionic-native/network';
|
||||
|
||||
|
@ -68,9 +68,11 @@ export class CoreAppProvider {
|
|||
protected logger;
|
||||
protected ssoAuthenticationPromise: Promise<any>;
|
||||
protected isKeyboardShown = false;
|
||||
protected backActions = [];
|
||||
|
||||
constructor(dbProvider: CoreDbProvider, private platform: Platform, private keyboard: Keyboard, private appCtrl: App,
|
||||
private network: Network, logger: CoreLoggerProvider, events: CoreEventsProvider, zone: NgZone) {
|
||||
private network: Network, logger: CoreLoggerProvider, events: CoreEventsProvider, zone: NgZone,
|
||||
private menuCtrl: MenuController) {
|
||||
this.logger = logger.getInstance('CoreAppProvider');
|
||||
this.db = dbProvider.getDB(this.DBNAME);
|
||||
|
||||
|
@ -92,6 +94,10 @@ export class CoreAppProvider {
|
|||
events.trigger(CoreEventsProvider.KEYBOARD_CHANGE, 0);
|
||||
});
|
||||
});
|
||||
|
||||
this.platform.registerBackButtonAction(() => {
|
||||
this.backButtonAction();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -382,4 +388,95 @@ export class CoreAppProvider {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement the backbutton actions pile.
|
||||
*/
|
||||
backButtonAction(): void {
|
||||
let x = 0;
|
||||
for (; x < this.backActions.length; x++) {
|
||||
if (this.backActions[x].priority < 1000) {
|
||||
break;
|
||||
}
|
||||
// Stop in the first action taken.
|
||||
if (this.backActions[x].fn()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Close open modals if any.
|
||||
if (this.menuCtrl && this.menuCtrl.isOpen()) {
|
||||
this.menuCtrl.close();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Remaining actions will have priority less than 1000.
|
||||
for (; x < this.backActions.length; x++) {
|
||||
if (this.backActions[x].priority < 500) {
|
||||
break;
|
||||
}
|
||||
// Stop in the first action taken.
|
||||
if (this.backActions[x].fn()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing found, go back.
|
||||
const navPromise = this.appCtrl.navPop();
|
||||
if (navPromise) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No views to go back to.
|
||||
|
||||
// Remaining actions will have priority less than 500.
|
||||
for (; x < this.backActions.length; x++) {
|
||||
// Stop in the first action taken.
|
||||
if (this.backActions[x].fn()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Ionic will decide (exit the app).
|
||||
this.appCtrl.goBack();
|
||||
}
|
||||
|
||||
/**
|
||||
* The back button event is triggered when the user presses the native
|
||||
* platform's back button, also referred to as the "hardware" back button.
|
||||
* This event is only used within Cordova apps running on Android and
|
||||
* Windows platforms. This event is not fired on iOS since iOS doesn't come
|
||||
* with a hardware back button in the same sense an Android or Windows device
|
||||
* does.
|
||||
*
|
||||
* Registering a hardware back button action and setting a priority allows
|
||||
* apps to control which action should be called when the hardware back
|
||||
* button is pressed. This method decides which of the registered back button
|
||||
* actions has the highest priority and should be called.
|
||||
*
|
||||
* @param {Function} fn Called when the back button is pressed,
|
||||
* if this registered action has the highest priority.
|
||||
* @param {number} priority Set the priority for this action. All actions sorted by priority will be executed since one of
|
||||
* them returns true.
|
||||
* * Priorities higher or equal than 1000 will go before closing modals
|
||||
* * Priorities lower than 500 will only be executed if you are in the first state of the app (before exit).
|
||||
* @returns {Function} A function that, when called, will unregister
|
||||
* the back button action.
|
||||
*/
|
||||
registerBackButtonAction(fn: Function, priority: number = 0): Function {
|
||||
const action = { fn: fn, priority: priority };
|
||||
|
||||
this.backActions.push(action);
|
||||
|
||||
this.backActions.sort((a, b) => {
|
||||
return b.priority - a.priority;
|
||||
});
|
||||
|
||||
return (): boolean => {
|
||||
const index = this.backActions.indexOf(action);
|
||||
|
||||
return index >= 0 && !!this.backActions.splice(index, 1);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue