MOBILE-3899 mainmenu: Hide main menu on navigation level > 1

main
Pau Ferrer Ocaña 2021-10-21 13:20:00 +02:00
parent 07fae0152a
commit d4b7de321c
7 changed files with 75 additions and 170 deletions

View File

@ -1439,8 +1439,6 @@
"core.completion-alt-manual-y-override": "completion", "core.completion-alt-manual-y-override": "completion",
"core.confirmcanceledit": "local_moodlemobileapp", "core.confirmcanceledit": "local_moodlemobileapp",
"core.confirmdeletefile": "repository", "core.confirmdeletefile": "repository",
"core.confirmgotabroot": "local_moodlemobileapp",
"core.confirmgotabrootdefault": "local_moodlemobileapp",
"core.confirmleaveunknownchanges": "local_moodlemobileapp", "core.confirmleaveunknownchanges": "local_moodlemobileapp",
"core.confirmloss": "local_moodlemobileapp", "core.confirmloss": "local_moodlemobileapp",
"core.confirmopeninbrowser": "local_moodlemobileapp", "core.confirmopeninbrowser": "local_moodlemobileapp",

View File

@ -46,7 +46,6 @@ import {
import { CoreFileHelper } from '@services/file-helper'; import { CoreFileHelper } from '@services/file-helper';
import { AddonModH5PActivityModuleHandlerService } from '../../services/handlers/module'; import { AddonModH5PActivityModuleHandlerService } from '../../services/handlers/module';
import { CoreMainMenuPage } from '@features/mainmenu/pages/menu/menu'; import { CoreMainMenuPage } from '@features/mainmenu/pages/menu/menu';
import { Platform } from '@singletons';
/** /**
* Component that displays an H5P activity entry page. * Component that displays an H5P activity entry page.
@ -81,13 +80,11 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
isOpeningPage = false; isOpeningPage = false;
canViewAllAttempts = false; canViewAllAttempts = false;
protected listeningResize = false;
protected fetchContentDefaultError = 'addon.mod_h5pactivity.errorgetactivity'; protected fetchContentDefaultError = 'addon.mod_h5pactivity.errorgetactivity';
protected syncEventName = AddonModH5PActivitySyncProvider.AUTO_SYNCED; protected syncEventName = AddonModH5PActivitySyncProvider.AUTO_SYNCED;
protected site: CoreSite; protected site: CoreSite;
protected observer?: CoreEventObserver; protected observer?: CoreEventObserver;
protected messageListenerFunction: (event: MessageEvent) => Promise<void>; protected messageListenerFunction: (event: MessageEvent) => Promise<void>;
protected resizeFunction: () => void;
constructor( constructor(
protected mainMenuPage: CoreMainMenuPage, protected mainMenuPage: CoreMainMenuPage,
@ -102,7 +99,6 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
// Listen for messages from the iframe. // Listen for messages from the iframe.
this.messageListenerFunction = this.onIframeMessage.bind(this); this.messageListenerFunction = this.onIframeMessage.bind(this);
window.addEventListener('message', this.messageListenerFunction); window.addEventListener('message', this.messageListenerFunction);
this.resizeFunction = this.contentResized.bind(this);
} }
/** /**
@ -375,8 +371,6 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
AddonModH5PActivity.logView(this.h5pActivity!.id, this.h5pActivity!.name, this.siteId); AddonModH5PActivity.logView(this.h5pActivity!.id, this.h5pActivity!.name, this.siteId);
CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata); CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
this.setResizeListener();
} }
/** /**
@ -506,45 +500,6 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
} }
} }
/**
* Set the resize listener if needed.
*/
setResizeListener(): void {
if (!this.playing || this.listeningResize) {
return;
}
this.listeningResize = true;
window.addEventListener('resize', this.contentResized.bind(this));
this.contentResized();
}
/**
* On content resize, change visibility of the main menu: show on portrait and hide on landscape.
*/
contentResized(): void {
this.mainMenuPage.changeVisibility(Platform.isPortrait());
}
/**
* @inheritdoc
*/
ionViewDidEnter(): void {
this.setResizeListener();
}
/**
* @inheritdoc
*/
ionViewWillLeave(): void {
this.mainMenuPage.changeVisibility(true);
if (this.listeningResize) {
this.listeningResize = false;
window.removeEventListener('resize', this.resizeFunction);
}
}
/** /**
* Component destroyed. * Component destroyed.
*/ */

View File

@ -74,7 +74,7 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
protected tabsElement?: HTMLElement; // The tabs parent element. It's the element that will be "scrolled" to hide tabs. protected tabsElement?: HTMLElement; // The tabs parent element. It's the element that will be "scrolled" to hide tabs.
protected tabBarElement?: HTMLIonTabBarElement; // The top tab bar element. protected tabBarElement?: HTMLIonTabBarElement; // The top tab bar element.
protected tabsShown = true; protected tabsShown = true;
protected resizeFunction?: EventListenerOrEventListenerObject; protected resizeFunction: EventListenerOrEventListenerObject;
protected isDestroyed = false; protected isDestroyed = false;
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.
@ -97,6 +97,8 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
protected element: ElementRef, protected element: ElementRef,
) { ) {
this.backButtonFunction = this.backButtonClicked.bind(this); this.backButtonFunction = this.backButtonClicked.bind(this);
this.resizeFunction = this.windowResized.bind(this);
this.tabAction = new CoreTabsRoleTab(this); this.tabAction = new CoreTabsRoleTab(this);
} }
@ -130,9 +132,7 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
await this.initializeTabs(); await this.initializeTabs();
} }
this.resizeFunction = this.windowResized.bind(this); window.addEventListener('resize', this.resizeFunction);
window.addEventListener('resize', this.resizeFunction!);
} }
/** /**
@ -162,17 +162,17 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
if (showTabs) { if (showTabs) {
// Smooth translation. // Smooth translation.
this.tabBarElement!.classList.remove('tabs-hidden'); this.tabBarElement.classList.remove('tabs-hidden');
if (scroll === 0) { if (scroll === 0) {
this.tabBarElement!.style.height = ''; this.tabBarElement.style.height = '';
this.previousLastScroll = this.lastScroll; this.previousLastScroll = this.lastScroll;
this.lastScroll = 0; this.lastScroll = 0;
} else if (scroll !== undefined) { } else if (scroll !== undefined) {
this.tabBarElement!.style.height = (this.tabBarHeight - scroll) + 'px'; this.tabBarElement.style.height = (this.tabBarHeight - scroll) + 'px';
} }
} else { } else {
this.tabBarElement!.classList.add('tabs-hidden'); this.tabBarElement.classList.add('tabs-hidden');
this.tabBarElement!.style.height = ''; this.tabBarElement.style.height = '';
} }
this.tabsShown = showTabs; this.tabsShown = showTabs;

View File

@ -1,22 +1,12 @@
<ion-tabs #mainTabs [hidden]="!showTabs" [class]="'placement-' + tabsPlacement" [class.tabshidden]="hidden" <ion-tabs #mainTabs [hidden]="!showTabs" [class]="'placement-' + tabsPlacement"
(ionTabsDidChange)="tabChanged($event)"> [class.tabshidden]="!isMainScreen && tabsPlacement == 'bottom'" (ionTabsDidChange)="tabChanged($event)">
<ion-tab-bar slot="bottom" [hidden]="hidden" class="mainmenu-tabs"> <ion-tab-bar slot="bottom" class="mainmenu-tabs"
[@menuShowHideAnimation]="tabsPlacement == 'side' ? '' : (isMainScreen ? 'visible' : 'hidden')">
<ion-spinner *ngIf="!loaded" [attr.aria-label]="'core.loading' | translate"></ion-spinner> <ion-spinner *ngIf="!loaded" [attr.aria-label]="'core.loading' | translate"></ion-spinner>
<ion-tab-button <ion-tab-button *ngFor="let tab of tabs" (keydown)="tabAction.keyDown($event)" (keyup)="tabAction.keyUp(tab.page, $event)"
*ngFor="let tab of tabs" [hidden]="!loaded && tab.hide" [tab]="tab.page" [disabled]="tab.hide" layout="label-hide" class="{{tab.class}}"
(click)="tabClicked($event, tab.page)" [selected]="tab.page === selectedTab" [tabindex]="selectedTab == tab.page ? 0 : -1" [attr.aria-controls]="tab.id">
(keydown)="tabAction.keyDown($event)"
(keyup)="tabAction.keyUp(tab.page, $event)"
[hidden]="!loaded && tab.hide"
[tab]="tab.page"
[disabled]="tab.hide"
layout="label-hide"
class="{{tab.class}}"
[selected]="tab.page === selectedTab"
[tabindex]="selectedTab == tab.page ? 0 : -1"
[attr.aria-controls]="tab.id"
>
<ion-icon [name]="tab.icon" aria-hidden="true"></ion-icon> <ion-icon [name]="tab.icon" aria-hidden="true"></ion-icon>
<ion-label aria-hidden="true">{{ tab.title | translate }}</ion-label> <ion-label aria-hidden="true">{{ tab.title | translate }}</ion-label>
<ion-badge *ngIf="tab.badge" aria-hidden="true">{{ tab.badge }}</ion-badge> <ion-badge *ngIf="tab.badge" aria-hidden="true">{{ tab.badge }}</ion-badge>
@ -26,16 +16,8 @@
</span> </span>
</ion-tab-button> </ion-tab-button>
<ion-tab-button <ion-tab-button (keydown)="tabAction.keyDown($event)" (keyup)="tabAction.keyUp(morePageName, $event)" [hidden]="!loaded"
(click)="tabClicked($event, morePageName)" [tab]="morePageName" layout="label-hide" [tabindex]="selectedTab == morePageName ? 0 : -1" [attr.aria-controls]="morePageName">
(keydown)="tabAction.keyDown($event)"
(keyup)="tabAction.keyUp(morePageName, $event)"
[hidden]="!loaded"
[tab]="morePageName"
layout="label-hide"
[tabindex]="selectedTab == morePageName ? 0 : -1"
[attr.aria-controls]="morePageName"
>
<ion-icon name="fas-bars" aria-hidden="true"></ion-icon> <ion-icon name="fas-bars" aria-hidden="true"></ion-icon>
<ion-label aria-hidden="true">{{ 'core.more' | translate }}</ion-label> <ion-label aria-hidden="true">{{ 'core.more' | translate }}</ion-label>
<span class="sr-only">{{ 'core.more' | translate }}</span> <span class="sr-only">{{ 'core.more' | translate }}</span>

View File

@ -9,9 +9,20 @@
} }
@if ($core-always-show-main-menu) { @if ($core-always-show-main-menu) {
ion-tab-bar[hidden] { ion-tabs.placement-bottom ion-tab-bar {
display: flex !important; height: var(--menutabbar-size) !important;
visibility: visible !important;
transform: translateY(0) !important;
} }
} @else {
ion-tabs.tabshidden.placement-bottom ion-tab-bar {
pointer-events: none;
ion-tab-button {
height: auto;
}
}
} }
ion-tab-button ion-icon { ion-tab-button ion-icon {
@ -56,12 +67,15 @@
@include border-end(var(--border)); @include border-end(var(--border));
box-shadow: 3px 0 3px rgba(var(--drop-shadow)); box-shadow: 3px 0 3px rgba(var(--drop-shadow));
border-top: 0; border-top: 0;
justify-content: flex-start;
@include padding(var(--ion-safe-area-top), 0px, var(--ion-safe-area-bottom), var(--ion-safe-area-left)); @include padding(var(--ion-safe-area-top), 0px, var(--ion-safe-area-bottom), var(--ion-safe-area-left));
ion-tab-button { ion-tab-button {
width: 100%; width: 100%;
height: auto; min-height: var(--menutabbar-size);
flex: 0;
ion-badge { ion-badge {
top: calc(50% - 20px); top: calc(50% - 20px);
} }

View File

@ -12,8 +12,7 @@
// 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, OnInit, OnDestroy, ViewChild, ChangeDetectorRef } from '@angular/core'; import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { IonTabs } from '@ionic/angular'; import { IonTabs } from '@ionic/angular';
import { BackButtonEvent } from '@ionic/core'; import { BackButtonEvent } from '@ionic/core';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
@ -22,11 +21,13 @@ import { CoreApp } from '@services/app';
import { CoreEvents, CoreEventObserver } from '@singletons/events'; import { CoreEvents, CoreEventObserver } from '@singletons/events';
import { CoreMainMenu, CoreMainMenuProvider } from '../../services/mainmenu'; import { CoreMainMenu, CoreMainMenuProvider } from '../../services/mainmenu';
import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from '../../services/mainmenu-delegate'; import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from '../../services/mainmenu-delegate';
import { CoreDomUtils } from '@services/utils/dom'; import { Router } from '@singletons';
import { Translate } from '@singletons';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
import { CoreAriaRoleTab, CoreAriaRoleTabFindable } from '@classes/aria-role-tab'; import { CoreAriaRoleTab, CoreAriaRoleTabFindable } from '@classes/aria-role-tab';
import { CoreNavigator } from '@services/navigator'; import { CoreNavigator } from '@services/navigator';
import { filter } from 'rxjs/operators';
import { NavigationEnd } from '@angular/router';
import { trigger, state, style, transition, animate } from '@angular/animations';
/** /**
* Page that displays the main menu of the app. * Page that displays the main menu of the app.
@ -34,6 +35,25 @@ import { CoreNavigator } from '@services/navigator';
@Component({ @Component({
selector: 'page-core-mainmenu', selector: 'page-core-mainmenu',
templateUrl: 'menu.html', templateUrl: 'menu.html',
animations: [
trigger('menuShowHideAnimation', [
state('hidden', style({
height: 0,
visibility: 'hidden',
transform: 'translateY(100%)',
})),
state('visible', style({
visibility: 'visible',
})),
transition('visible => hidden', [
style({ transform: 'translateY(0)' }),
animate('500ms ease-in-out', style({ transform: 'translateY(100%)' })),
]),
transition('hidden => visible', [
style({ transform: 'translateY(100%)', visibility: 'visible', height: '*' }),
animate('500ms ease-in-out', style({ transform: 'translateY(0)' })),
]),
])],
styleUrls: ['menu.scss'], styleUrls: ['menu.scss'],
}) })
export class CoreMainMenuPage implements OnInit, OnDestroy { export class CoreMainMenuPage implements OnInit, OnDestroy {
@ -43,11 +63,12 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
loaded = false; loaded = false;
showTabs = false; showTabs = false;
tabsPlacement: 'bottom' | 'side' = 'bottom'; tabsPlacement: 'bottom' | 'side' = 'bottom';
hidden = false;
morePageName = CoreMainMenuProvider.MORE_PAGE_NAME; morePageName = CoreMainMenuProvider.MORE_PAGE_NAME;
selectedTab?: string; selectedTab?: string;
isMainScreen = false;
protected subscription?: Subscription; protected subscription?: Subscription;
protected navSubscription?: Subscription;
protected keyboardObserver?: CoreEventObserver; protected keyboardObserver?: CoreEventObserver;
protected resizeFunction: () => void; protected resizeFunction: () => void;
protected backButtonFunction: (event: BackButtonEvent) => void; protected backButtonFunction: (event: BackButtonEvent) => void;
@ -58,21 +79,27 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
tabAction: CoreMainMenuRoleTab; tabAction: CoreMainMenuRoleTab;
constructor( constructor() {
protected route: ActivatedRoute,
protected changeDetector: ChangeDetectorRef,
) {
this.resizeFunction = this.initHandlers.bind(this); this.resizeFunction = this.initHandlers.bind(this);
this.backButtonFunction = this.backButtonClicked.bind(this); this.backButtonFunction = this.backButtonClicked.bind(this);
this.tabAction = new CoreMainMenuRoleTab(this); this.tabAction = new CoreMainMenuRoleTab(this);
// Listen navigation events to show or hide tabs.
this.navSubscription = Router.events
.pipe(filter(event => event instanceof NavigationEnd))
.subscribe(async () => {
this.isMainScreen = !this.mainTabs?.outlet.canGoBack();
});
} }
/** /**
* Initialize the component. * @inheritdoc
*/ */
ngOnInit(): void { async ngOnInit(): Promise<void> {
this.showTabs = true; this.showTabs = true;
this.isMainScreen = !this.mainTabs?.outlet.canGoBack();
this.subscription = CoreMainMenuDelegate.getHandlersObservable().subscribe((handlers) => { this.subscription = CoreMainMenuDelegate.getHandlersObservable().subscribe((handlers) => {
// Remove the handlers that should only appear in the More menu. // Remove the handlers that should only appear in the More menu.
this.allHandlers = handlers.filter((handler) => !handler.onlyInMore); this.allHandlers = handlers.filter((handler) => !handler.onlyInMore);
@ -134,78 +161,16 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
} }
/** /**
* Change tabs visibility to show/hide them from the view. * @inheritdoc
*
* @param visible If show or hide the tabs.
*/
changeVisibility(visible: boolean): void {
if (this.hidden == visible) {
// Change needed.
this.hidden = !visible;
/* setTimeout(() => {
this.viewCtrl.getContent().resize();
});*/
}
}
/**
* Page destroyed.
*/ */
ngOnDestroy(): void { ngOnDestroy(): void {
this.subscription?.unsubscribe(); this.subscription?.unsubscribe();
this.navSubscription?.unsubscribe();
window.removeEventListener('resize', this.resizeFunction); window.removeEventListener('resize', this.resizeFunction);
document.removeEventListener('ionBackButton', this.backButtonFunction); document.removeEventListener('ionBackButton', this.backButtonFunction);
this.keyboardObserver?.off(); this.keyboardObserver?.off();
} }
/**
* Tab clicked.
*
* @param e Event.
* @param page Page of the tab.
*/
async tabClicked(e: Event, page: string): Promise<void> {
if (this.mainTabs?.getSelected() != page) {
// Just change the tab.
return;
}
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
// Current tab was clicked. Check if user is already at root level.
const isMainMenuRoot = await this.currentRouteIsMainMenuRoot();
if (isMainMenuRoot) {
return; // Already at root level, nothing to do.
}
// Maybe the route isn't defined as it should. Check if the current path is the tab one.
const currentPath = CoreNavigator.getCurrentPath();
if (currentPath == `/main/${page}`) {
return; // Already at root level, nothing to do.
}
// Ask the user if he wants to go back to the root page of the tab.
try {
const tab = this.tabs.find((tab) => tab.page == page);
if (tab?.title) {
await CoreDomUtils.showConfirm(Translate.instant('core.confirmgotabroot', {
name: Translate.instant(tab.title),
}));
} else {
await CoreDomUtils.showConfirm(Translate.instant('core.confirmgotabrootdefault'));
}
// User confirmed, go to root.
this.mainTabs?.select(page);
} catch {
// User canceled.
}
}
/** /**
* Selected tab has changed. * Selected tab has changed.
* *
@ -274,13 +239,6 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
*/ */
class CoreMainMenuRoleTab extends CoreAriaRoleTab<CoreMainMenuPage> { class CoreMainMenuRoleTab extends CoreAriaRoleTab<CoreMainMenuPage> {
/**
* @inheritdoc
*/
selectTab(tabId: string, e: Event): void {
this.componentInstance.tabClicked(e, tabId);
}
/** /**
* @inheritdoc * @inheritdoc
*/ */

View File

@ -47,8 +47,6 @@
"completion-alt-manual-y-override": "Completed: {{$a.modname}} (set by {{$a.overrideuser}}). Select to mark as not complete.", "completion-alt-manual-y-override": "Completed: {{$a.modname}} (set by {{$a.overrideuser}}). Select to mark as not complete.",
"confirmcanceledit": "Are you sure you want to leave this page? All changes will be lost.", "confirmcanceledit": "Are you sure you want to leave this page? All changes will be lost.",
"confirmdeletefile": "Are you sure you want to delete this file?", "confirmdeletefile": "Are you sure you want to delete this file?",
"confirmgotabroot": "Are you sure you want to go back to {{name}}?",
"confirmgotabrootdefault": "Are you sure you want to go to the initial page of the current tab?",
"confirmleaveunknownchanges": "Are you sure you want to leave this page? If you have unsaved changes they will be lost.", "confirmleaveunknownchanges": "Are you sure you want to leave this page? If you have unsaved changes they will be lost.",
"confirmloss": "Are you sure? All changes will be lost.", "confirmloss": "Are you sure? All changes will be lost.",
"confirmopeninbrowser": "Do you want to open it in a web browser?", "confirmopeninbrowser": "Do you want to open it in a web browser?",