MOBILE-2738 tabs: Control navigation when backbutton is actioned
This commit is contained in:
		
							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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user