MOBILE-3708 android: Handle back button in tabs

main
Dani Palou 2021-02-25 12:16:23 +01:00
parent 81bf906fef
commit 3d80e57402
5 changed files with 92 additions and 56 deletions

View File

@ -115,6 +115,8 @@ export class AppComponent implements OnInit, AfterViewInit {
}); });
this.onPlatformReady(); this.onPlatformReady();
// @todo: Quit app with back button. How to tell if we're at root level?
} }
/** /**

View File

@ -25,9 +25,9 @@ import {
ElementRef, ElementRef,
} from '@angular/core'; } from '@angular/core';
import { IonSlides } from '@ionic/angular'; import { IonSlides } from '@ionic/angular';
import { BackButtonEvent } from '@ionic/core';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { CoreApp } from '@services/app';
import { Platform, Translate } from '@singletons'; import { Platform, Translate } from '@singletons';
import { CoreSettingsHelper } from '@features/settings/services/settings-helper'; import { CoreSettingsHelper } from '@features/settings/services/settings-helper';
@ -81,7 +81,7 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
protected selectHistory: string[] = []; protected selectHistory: string[] = [];
protected firstSelectedTab?: string; // ID of the first selected tab to control history. protected firstSelectedTab?: string; // ID of the first selected tab to control history.
protected unregisterBackButtonAction: any; protected backButtonFunction: (event: BackButtonEvent) => void;
protected languageChangedSubscription?: Subscription; protected languageChangedSubscription?: Subscription;
protected isInTransition = false; // Weather Slides is in transition. protected isInTransition = false; // Weather Slides is in transition.
protected slidesSwiper: any; // eslint-disable-line @typescript-eslint/no-explicit-any protected slidesSwiper: any; // eslint-disable-line @typescript-eslint/no-explicit-any
@ -91,6 +91,7 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
constructor( constructor(
protected element: ElementRef, protected element: ElementRef,
) { ) {
this.backButtonFunction = this.backButtonClicked.bind(this);
} }
/** /**
@ -171,43 +172,47 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
this.calculateSlides(); this.calculateSlides();
this.registerBackButtonAction(); document.addEventListener('ionBackButton', this.backButtonFunction);
} }
/** /**
* Register back button action. * Back button clicked.
*
* @param event Event.
*/ */
protected registerBackButtonAction(): void { protected backButtonClicked(event: BackButtonEvent): void {
this.unregisterBackButtonAction = CoreApp.registerBackButtonAction(() => { event.detail.register(40, (processNextHandler: () => void) => {
// The previous page in history is not the last one, we need the previous one.
if (this.selectHistory.length > 1) { if (this.selectHistory.length > 1) {
const tabIndex = this.selectHistory[this.selectHistory.length - 2]; // The previous page in history is not the last one, we need the previous one.
const previousTabId = this.selectHistory[this.selectHistory.length - 2];
// Remove curent and previous tabs from history. // Remove curent and previous tabs from history.
this.selectHistory = this.selectHistory.filter((tabId) => this.selected != tabId && tabIndex != tabId); this.selectHistory = this.selectHistory.filter((tabId) => this.selected != tabId && previousTabId != tabId);
this.selectTab(tabIndex); this.selectTab(previousTabId);
return true; return;
} else if (this.selected != this.firstSelectedTab) { }
if (this.firstSelectedTab && this.selected != this.firstSelectedTab) {
// All history is gone but we are not in the first selected tab. // All history is gone but we are not in the first selected tab.
this.selectHistory = []; this.selectHistory = [];
this.selectTab(this.firstSelectedTab!); this.selectTab(this.firstSelectedTab);
return true; return;
} }
return false; processNextHandler();
}, 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 // Unregister the custom back button action for this component.
this.unregisterBackButtonAction && this.unregisterBackButtonAction(); document.removeEventListener('ionBackButton', this.backButtonFunction);
this.isCurrentView = false; this.isCurrentView = false;
} }

View File

@ -1,4 +1,5 @@
<ion-tabs #mainTabs [hidden]="!showTabs" [class]="'placement-' + tabsPlacement" [class.tabshidden]="hidden"> <ion-tabs #mainTabs [hidden]="!showTabs" [class]="'placement-' + tabsPlacement" [class.tabshidden]="hidden"
(ionTabsDidChange)="tabChanged($event)">
<ion-tab-bar slot="bottom" [hidden]="hidden"> <ion-tab-bar slot="bottom" [hidden]="hidden">
<ion-spinner *ngIf="!loaded"></ion-spinner> <ion-spinner *ngIf="!loaded"></ion-spinner>

View File

@ -15,6 +15,7 @@
import { Component, OnInit, OnDestroy, ViewChild, ChangeDetectorRef } from '@angular/core'; import { Component, OnInit, OnDestroy, ViewChild, ChangeDetectorRef } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { IonTabs } from '@ionic/angular'; import { IonTabs } from '@ionic/angular';
import { BackButtonEvent } from '@ionic/core';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { CoreApp } from '@services/app'; import { CoreApp } from '@services/app';
@ -50,6 +51,11 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
protected pendingRedirect?: CoreRedirectPayload; protected pendingRedirect?: CoreRedirectPayload;
protected urlToOpen?: string; protected urlToOpen?: string;
protected keyboardObserver?: CoreEventObserver; protected keyboardObserver?: CoreEventObserver;
protected resizeFunction: () => void;
protected backButtonFunction: (event: BackButtonEvent) => void;
protected selectHistory: string[] = [];
protected selectedTab?: string;
protected firstSelectedTab?: string;
@ViewChild('mainTabs') mainTabs?: IonTabs; @ViewChild('mainTabs') mainTabs?: IonTabs;
@ -57,7 +63,10 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
protected route: ActivatedRoute, protected route: ActivatedRoute,
protected changeDetector: ChangeDetectorRef, protected changeDetector: ChangeDetectorRef,
protected router: Router, protected router: Router,
) {} ) {
this.resizeFunction = this.initHandlers.bind(this);
this.backButtonFunction = this.backButtonClicked.bind(this);
}
/** /**
* Initialize the component. * Initialize the component.
@ -100,7 +109,8 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
} }
}); });
window.addEventListener('resize', this.initHandlers.bind(this)); window.addEventListener('resize', this.resizeFunction);
document.addEventListener('ionBackButton', this.backButtonFunction);
if (CoreApp.isIOS()) { if (CoreApp.isIOS()) {
// In iOS, the resize event is triggered before the keyboard is opened/closed and not triggered again once done. // In iOS, the resize event is triggered before the keyboard is opened/closed and not triggered again once done.
@ -209,7 +219,8 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
ngOnDestroy(): void { ngOnDestroy(): void {
this.subscription?.unsubscribe(); this.subscription?.unsubscribe();
this.redirectObs?.off(); this.redirectObs?.off();
window.removeEventListener('resize', this.initHandlers.bind(this)); window.removeEventListener('resize', this.resizeFunction);
document.removeEventListener('ionBackButton', this.backButtonFunction);
this.keyboardObserver?.off(); this.keyboardObserver?.off();
} }
@ -262,4 +273,46 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
} }
} }
/**
* Selected tab has changed.
*
* @param event Event.
*/
tabChanged(event: {tab: string}): void {
this.selectedTab = event.tab;
this.firstSelectedTab = this.firstSelectedTab ?? event.tab;
this.selectHistory.push(event.tab);
}
/**
* Back button clicked.
*
* @param event Event.
*/
protected backButtonClicked(event: BackButtonEvent): void {
event.detail.register(20, (processNextHandler: () => void) => {
if (this.selectHistory.length > 1) {
// The previous page in history is not the last one, we need the previous one.
const previousTab = this.selectHistory[this.selectHistory.length - 2];
// Remove curent and previous tabs from history.
this.selectHistory = this.selectHistory.filter((tab) => this.selectedTab != tab && previousTab != tab);
this.mainTabs?.select(previousTab);
return;
}
if (this.firstSelectedTab && this.selectedTab != this.firstSelectedTab) {
// All history is gone but we are not in the first selected tab.
this.selectHistory = [];
this.mainTabs?.select(this.firstSelectedTab);
return;
}
processNextHandler();
});
}
} }

View File

@ -56,7 +56,6 @@ export class CoreAppProvider {
protected isKeyboardShown = false; protected isKeyboardShown = false;
protected keyboardOpening = false; protected keyboardOpening = false;
protected keyboardClosing = false; protected keyboardClosing = false;
protected backActions: {callback: () => boolean; priority: number}[] = [];
protected forceOffline = false; protected forceOffline = false;
protected redirect?: CoreRedirectData; protected redirect?: CoreRedirectData;
@ -68,11 +67,6 @@ export class CoreAppProvider {
this.schemaVersionsManager = new Promise(resolve => this.resolveSchemaVersionsManager = resolve); this.schemaVersionsManager = new Promise(resolve => this.resolveSchemaVersionsManager = resolve);
this.db = CoreDB.getDB(DBNAME); this.db = CoreDB.getDB(DBNAME);
this.logger = CoreLogger.getInstance('CoreAppProvider'); this.logger = CoreLogger.getInstance('CoreAppProvider');
// @todo
// this.platform.registerBackButtonAction(() => {
// this.backButtonAction();
// }, 100);
} }
/** /**
@ -592,37 +586,18 @@ export class CoreAppProvider {
} }
/** /**
* The back button event is triggered when the user presses the native * Register a back button action.
* platform's back button, also referred to as the "hardware" back button. * This function is deprecated and no longer works. You should now use Ionic events directly, please see:
* This event is only used within Cordova apps running on Android and * https://ionicframework.com/docs/developing/hardware-back-button
* 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 * @param callback Called when the back button is pressed.
* apps to control which action should be called when the hardware back * @param priority Priority.
* button is pressed. This method decides which of the registered back button
* actions has the highest priority and should be called.
*
* @param callback Called when the back button is pressed, if this registered action has the highest priority.
* @param 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).
* @return A function that, when called, will unregister the back button action. * @return A function that, when called, will unregister the back button action.
* @deprecated since 3.9.5
*/ */
registerBackButtonAction(callback: () => boolean, priority: number = 0): () => boolean { // eslint-disable-next-line @typescript-eslint/no-unused-vars
const action = { callback, priority }; registerBackButtonAction(callback: () => boolean, priority = 0): () => boolean {
return () => false;
this.backActions.push(action);
this.backActions.sort((a, b) => b.priority - a.priority);
return (): boolean => {
const index = this.backActions.indexOf(action);
return index >= 0 && !!this.backActions.splice(index, 1);
};
} }
/** /**