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
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// 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 { Tabs, NavController, ViewController, App, Config, Platform, DeepLinker, Keyboard, RootNode } from 'ionic-angular';
|
||||||
import { CoreIonTabComponent } from './ion-tab';
|
import { CoreIonTabComponent } from './ion-tab';
|
||||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
|
import { CoreAppProvider } from '@providers/app';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Equivalent to ion-tabs. It has 2 improvements:
|
* Equivalent to ion-tabs. It has 2 improvements:
|
||||||
|
@ -28,7 +30,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
encapsulation: ViewEncapsulation.None,
|
encapsulation: ViewEncapsulation.None,
|
||||||
providers: [{provide: RootNode, useExisting: forwardRef(() => CoreIonTabsComponent) }]
|
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.
|
* 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 viewInit = false; // Whether the view has been initialized.
|
||||||
protected initialized = false; // Whether tabs have been initialized.
|
protected initialized = false; // Whether tabs have been initialized.
|
||||||
|
|
||||||
constructor(protected utils: CoreUtilsProvider, @Optional() parent: NavController, @Optional() viewCtrl: ViewController,
|
protected firstSelectedTab: string;
|
||||||
_app: App, config: Config, elementRef: ElementRef, _plt: Platform, renderer: Renderer, _linker: DeepLinker,
|
protected unregisterBackButtonAction: any;
|
||||||
keyboard?: Keyboard) {
|
|
||||||
|
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);
|
super(parent, viewCtrl, _app, config, elementRef, _plt, renderer, _linker, keyboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,6 +80,8 @@ export class CoreIonTabsComponent extends Tabs {
|
||||||
this.viewInit = true;
|
this.viewInit = true;
|
||||||
|
|
||||||
super.ngAfterViewInit();
|
super.ngAfterViewInit();
|
||||||
|
|
||||||
|
this.registerBackButtonAction();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -146,6 +153,8 @@ export class CoreIonTabsComponent extends Tabs {
|
||||||
this.select(tab);
|
this.select(tab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.firstSelectedTab = this._selectHistory[0] || null;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Tabs not loaded yet. Set the tab bar position so the tab bar is shown, it'll have a spinner.
|
// 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.
|
* 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,
|
Component, Input, Output, EventEmitter, OnInit, OnChanges, OnDestroy, AfterViewInit, ViewChild, ElementRef,
|
||||||
SimpleChange
|
SimpleChange
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { CoreTabComponent } from './tab';
|
|
||||||
import { Content, Slides } from 'ionic-angular';
|
import { Content, Slides } from 'ionic-angular';
|
||||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
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.
|
* 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 isCurrentView = true;
|
||||||
protected shouldSlideToInitial = false; // Whether we need to slide to the initial slide because it's out of view.
|
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 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;
|
this.tabBarElement = element.nativeElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,12 +134,47 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe
|
||||||
if (this.initialized) {
|
if (this.initialized) {
|
||||||
this.calculateSlides();
|
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.
|
* User left the page that contains the component.
|
||||||
*/
|
*/
|
||||||
ionViewDidLeave(): void {
|
ionViewDidLeave(): void {
|
||||||
|
// Unregister the custom back button action for this page
|
||||||
|
this.unregisterBackButtonAction && this.unregisterBackButtonAction();
|
||||||
|
|
||||||
this.isCurrentView = false;
|
this.isCurrentView = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,6 +270,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedTab) {
|
if (selectedTab) {
|
||||||
|
this.firstSelectedTab = selectedIndex;
|
||||||
this.selectTab(selectedIndex);
|
this.selectTab(selectedIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -405,6 +447,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe
|
||||||
this.slides.slideTo(index);
|
this.slides.slideTo(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.selectHistory.push(index);
|
||||||
this.selected = index;
|
this.selected = index;
|
||||||
newTab.selectTab();
|
newTab.selectTab();
|
||||||
this.ionChange.emit(newTab);
|
this.ionChange.emit(newTab);
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Injectable, NgZone } from '@angular/core';
|
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 { Keyboard } from '@ionic-native/keyboard';
|
||||||
import { Network } from '@ionic-native/network';
|
import { Network } from '@ionic-native/network';
|
||||||
|
|
||||||
|
@ -68,9 +68,11 @@ export class CoreAppProvider {
|
||||||
protected logger;
|
protected logger;
|
||||||
protected ssoAuthenticationPromise: Promise<any>;
|
protected ssoAuthenticationPromise: Promise<any>;
|
||||||
protected isKeyboardShown = false;
|
protected isKeyboardShown = false;
|
||||||
|
protected backActions = [];
|
||||||
|
|
||||||
constructor(dbProvider: CoreDbProvider, private platform: Platform, private keyboard: Keyboard, private appCtrl: App,
|
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.logger = logger.getInstance('CoreAppProvider');
|
||||||
this.db = dbProvider.getDB(this.DBNAME);
|
this.db = dbProvider.getDB(this.DBNAME);
|
||||||
|
|
||||||
|
@ -92,6 +94,10 @@ export class CoreAppProvider {
|
||||||
events.trigger(CoreEventsProvider.KEYBOARD_CHANGE, 0);
|
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