MOBILE-3881 menu: Add badge on more tab
parent
f765976247
commit
375301b788
|
@ -23,11 +23,11 @@ import { CoreSites } from '@services/sites';
|
|||
import { CoreEvents } from '@singletons/events';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import {
|
||||
CorePushNotifications,
|
||||
CorePushNotificationsNotificationBasicData,
|
||||
} from '@features/pushnotifications/services/pushnotifications';
|
||||
import { CorePushNotificationsDelegate } from '@features/pushnotifications/services/push-delegate';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { CoreMainMenuProvider } from '@features/mainmenu/services/mainmenu';
|
||||
|
||||
/**
|
||||
* Handler to inject an option into main menu.
|
||||
|
@ -90,7 +90,7 @@ export class AddonMessagesMainMenuHandlerService implements CoreMainMenuHandler,
|
|||
);
|
||||
|
||||
// Register Badge counter.
|
||||
CorePushNotificationsDelegate.registerCounterHandler('AddonMessages');
|
||||
CorePushNotificationsDelegate.registerCounterHandler(AddonMessagesMainMenuHandlerService.name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -162,7 +162,14 @@ export class AddonMessagesMainMenuHandlerService implements CoreMainMenuHandler,
|
|||
}
|
||||
|
||||
// Update push notifications badge.
|
||||
CorePushNotifications.updateAddonCounter('AddonMessages', totalCount, siteId);
|
||||
CoreEvents.trigger(
|
||||
CoreMainMenuProvider.MAIN_MENU_HANDLER_BADGE_UPDATED,
|
||||
{
|
||||
handler: AddonMessagesMainMenuHandlerService.name,
|
||||
value: totalCount,
|
||||
},
|
||||
siteId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,6 +22,7 @@ import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@features/mainmenu
|
|||
import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications';
|
||||
import { CorePushNotificationsDelegate } from '@features/pushnotifications/services/push-delegate';
|
||||
import { AddonNotifications, AddonNotificationsProvider } from '../notifications';
|
||||
import { CoreMainMenuProvider } from '@features/mainmenu/services/mainmenu';
|
||||
|
||||
/**
|
||||
* Handler to inject an option into main menu.
|
||||
|
@ -72,7 +73,7 @@ export class AddonNotificationsMainMenuHandlerService implements CoreMainMenuHan
|
|||
});
|
||||
|
||||
// Register Badge counter.
|
||||
CorePushNotificationsDelegate.registerCounterHandler('AddonNotifications');
|
||||
CorePushNotificationsDelegate.registerCounterHandler(AddonNotificationsMainMenuHandlerService.name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -112,10 +113,20 @@ export class AddonNotificationsMainMenuHandlerService implements CoreMainMenuHan
|
|||
try {
|
||||
const unreadCountData = await AddonNotifications.getUnreadNotificationsCount(undefined, siteId);
|
||||
|
||||
this.handlerData.badge = unreadCountData.count > 0 ?
|
||||
unreadCountData.count + (unreadCountData.hasMore ? '+' : '') :
|
||||
'';
|
||||
CorePushNotifications.updateAddonCounter('AddonNotifications', unreadCountData.count, siteId);
|
||||
this.handlerData.badge = unreadCountData.count > 0
|
||||
? unreadCountData.count + (unreadCountData.hasMore ? '+' : '')
|
||||
: '';
|
||||
|
||||
CorePushNotifications.updateAddonCounter(AddonNotificationsMainMenuHandlerService.name, unreadCountData.count, siteId);
|
||||
|
||||
CoreEvents.trigger(
|
||||
CoreMainMenuProvider.MAIN_MENU_HANDLER_BADGE_UPDATED,
|
||||
{
|
||||
handler: AddonNotificationsMainMenuHandlerService.name,
|
||||
value: unreadCountData.count,
|
||||
},
|
||||
siteId,
|
||||
);
|
||||
} catch {
|
||||
this.handlerData.badge = '';
|
||||
} finally {
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
<ion-tab-button *ngFor="let tab of tabs" (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 class="core-tab-icon" [name]="tab.icon" aria-hidden="true"></ion-icon>
|
||||
<ion-label aria-hidden="true">{{ tab.title | translate }}</ion-label>
|
||||
<ion-badge *ngIf="tab.badge" aria-hidden="true">{{ tab.badge }}</ion-badge>
|
||||
<ion-badge class="core-tab-badge" *ngIf="tab.badge" aria-hidden="true">{{ tab.badge }}</ion-badge>
|
||||
<span class="sr-only">{{ tab.title | translate }}</span>
|
||||
<span *ngIf="tab.badge && tab.badgeA11yText" class="sr-only">
|
||||
{{ tab.badgeA11yText | translate: {$a : tab.badge } }}
|
||||
|
@ -20,9 +20,10 @@
|
|||
|
||||
<ion-tab-button (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="ellipsis-horizontal" aria-hidden="true"></ion-icon>
|
||||
<ion-icon class="core-tab-icon" name="ellipsis-horizontal" aria-hidden="true"></ion-icon>
|
||||
<ion-label aria-hidden="true">{{ 'core.more' | translate }}</ion-label>
|
||||
<span class="sr-only">{{ 'core.more' | translate }}</span>
|
||||
<ion-icon *ngIf="moreBadge" class="core-tab-badge" name="fas-circle" aria-hidden="true"></ion-icon>
|
||||
</ion-tab-button>
|
||||
</ion-tab-bar>
|
||||
</ion-tabs>
|
||||
|
|
|
@ -25,17 +25,17 @@
|
|||
|
||||
}
|
||||
|
||||
ion-tab-button ion-icon {
|
||||
ion-tab-button ion-icon.core-tab-icon {
|
||||
text-overflow: unset;
|
||||
overflow: visible;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
ion-tab-button.ios ion-icon {
|
||||
ion-tab-button.ios ion-icon.core-tab-icon {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
ion-tab-button.md ion-badge {
|
||||
ion-tab-button.md ion-badge.core-tab-badge {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
border-radius: 10px;
|
||||
|
@ -48,11 +48,29 @@
|
|||
background: var(--background-selected);
|
||||
}
|
||||
|
||||
ion-icon.core-tab-badge {
|
||||
color: var(--core-bottom-tabs-badge-color);
|
||||
padding: 3px 6px 2px;
|
||||
@include position(8px, null, null, calc(50% + 6px));
|
||||
min-width: 12px;
|
||||
font-size: 8px;
|
||||
font-weight: normal;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
ion-badge.core-tab-badge {
|
||||
--background: var(--core-bottom-tabs-badge-color);
|
||||
--color: var(--core-bottom-tabs-badge-text-color);
|
||||
}
|
||||
|
||||
ion-tabs.placement-bottom ion-tab-button {
|
||||
ion-icon {
|
||||
ion-icon.core-tab-icon {
|
||||
transition: margin 500ms ease-in-out, transform 300ms ease-in-out;
|
||||
}
|
||||
ion-badge {
|
||||
ion-icon.core-tab-badge,
|
||||
ion-badge.core-tab-badge {
|
||||
top: 8px;
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +94,8 @@
|
|||
min-height: var(--menutabbar-size);
|
||||
flex: 0;
|
||||
|
||||
ion-badge {
|
||||
ion-icon.core-tab-badge,
|
||||
ion-badge.core-tab-badge {
|
||||
top: calc(50% - 20px);
|
||||
}
|
||||
}
|
||||
|
@ -114,11 +133,11 @@
|
|||
|
||||
:host-context(.core-online),
|
||||
:host-context(.core-offline) {
|
||||
ion-tabs.placement-bottom ion-tab-button ion-icon {
|
||||
ion-tabs.placement-bottom ion-tab-button ion-icon.core-tab-icon {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
ion-tabs.placement-bottom ion-tab-button.ios ion-icon {
|
||||
ion-tabs.placement-bottom ion-tab-button.ios ion-icon.core-tab-icon {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ import { CoreNavigator } from '@services/navigator';
|
|||
import { filter } from 'rxjs/operators';
|
||||
import { NavigationEnd } from '@angular/router';
|
||||
import { trigger, state, style, transition, animate } from '@angular/animations';
|
||||
import { CoreSites } from '@services/sites';
|
||||
|
||||
/**
|
||||
* Page that displays the main menu of the app.
|
||||
|
@ -66,10 +67,12 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
|
|||
morePageName = CoreMainMenuProvider.MORE_PAGE_NAME;
|
||||
selectedTab?: string;
|
||||
isMainScreen = false;
|
||||
moreBadge = false;
|
||||
|
||||
protected subscription?: Subscription;
|
||||
protected navSubscription?: Subscription;
|
||||
protected keyboardObserver?: CoreEventObserver;
|
||||
protected badgeUpdateObserver?: CoreEventObserver;
|
||||
protected resizeFunction: () => void;
|
||||
protected backButtonFunction: (event: BackButtonEvent) => void;
|
||||
protected selectHistory: string[] = [];
|
||||
|
@ -102,11 +105,17 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
|
|||
|
||||
this.subscription = CoreMainMenuDelegate.getHandlersObservable().subscribe((handlers) => {
|
||||
// Remove the handlers that should only appear in the More menu.
|
||||
this.allHandlers = handlers.filter((handler) => !handler.onlyInMore);
|
||||
this.allHandlers = handlers;
|
||||
|
||||
this.initHandlers();
|
||||
});
|
||||
|
||||
this.badgeUpdateObserver = CoreEvents.on(CoreMainMenuProvider.MAIN_MENU_HANDLER_BADGE_UPDATED, (data) => {
|
||||
if (data.siteId == CoreSites.getCurrentSiteId()) {
|
||||
this.updateMoreBadge();
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('resize', this.resizeFunction);
|
||||
document.addEventListener('ionBackButton', this.backButtonFunction);
|
||||
|
||||
|
@ -130,10 +139,14 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
|
|||
* Init handlers on change (size or handlers).
|
||||
*/
|
||||
initHandlers(): void {
|
||||
if (this.allHandlers) {
|
||||
if (!this.allHandlers) {
|
||||
return;
|
||||
}
|
||||
this.tabsPlacement = CoreMainMenu.getTabPlacement();
|
||||
|
||||
const handlers = this.allHandlers.slice(0, CoreMainMenu.getNumItems()); // Get main handlers.
|
||||
const handlers = this.allHandlers
|
||||
.filter((handler) => !handler.onlyInMore)
|
||||
.slice(0, CoreMainMenu.getNumItems()); // Get main handlers.
|
||||
|
||||
// Re-build the list of tabs. If a handler is already in the list, use existing object to prevent re-creating the tab.
|
||||
const newTabs: CoreMainMenuHandlerToDisplay[] = [];
|
||||
|
@ -156,8 +169,22 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
|
|||
// Sort them by priority so new handlers are in the right position.
|
||||
this.tabs.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
||||
|
||||
this.updateMoreBadge();
|
||||
|
||||
this.loaded = CoreMainMenuDelegate.areHandlersLoaded();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check all non visible tab handlers for any badge text or number.
|
||||
*/
|
||||
updateMoreBadge(): void {
|
||||
if (!this.allHandlers) {
|
||||
return;
|
||||
}
|
||||
|
||||
const numItems = CoreMainMenu.getNumItems();
|
||||
this.moreBadge = this.allHandlers.some((handler, index) => (handler.onlyInMore || index >= numItems) && !!handler.badge);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -169,6 +196,7 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
|
|||
window.removeEventListener('resize', this.resizeFunction);
|
||||
document.removeEventListener('ionBackButton', this.backButtonFunction);
|
||||
this.keyboardObserver?.off();
|
||||
this.badgeUpdateObserver?.off();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -23,6 +23,19 @@ import { Device, makeSingleton } from '@singletons';
|
|||
import { CoreArray } from '@singletons/array';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
|
||||
declare module '@singletons/events' {
|
||||
|
||||
/**
|
||||
* Augment CoreEventsData interface with events specific to this service.
|
||||
*
|
||||
* @see https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
|
||||
*/
|
||||
export interface CoreEventsData {
|
||||
[CoreMainMenuProvider.MAIN_MENU_HANDLER_BADGE_UPDATED]: CoreMainMenuHandlerBadgeUpdatedEventData;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Service that provides some features regarding Main Menu.
|
||||
*/
|
||||
|
@ -32,6 +45,7 @@ export class CoreMainMenuProvider {
|
|||
static readonly NUM_MAIN_HANDLERS = 4;
|
||||
static readonly ITEM_MIN_WIDTH = 72; // Min with of every item, based on 5 items on a 360 pixel wide screen.
|
||||
static readonly MORE_PAGE_NAME = 'more';
|
||||
static readonly MAIN_MENU_HANDLER_BADGE_UPDATED = 'main_menu_handler_badge_updated';
|
||||
|
||||
protected tablet = false;
|
||||
|
||||
|
@ -339,3 +353,8 @@ type CustomMenuItemsMap = Record<string, {
|
|||
};
|
||||
};
|
||||
}>;
|
||||
|
||||
export type CoreMainMenuHandlerBadgeUpdatedEventData = {
|
||||
handler: string; // Handler name.
|
||||
value: number; // New counter value.
|
||||
};
|
||||
|
|
|
@ -41,6 +41,7 @@ import {
|
|||
import { CoreError } from '@classes/errors/error';
|
||||
import { CoreWSExternalWarning } from '@services/ws';
|
||||
import { CoreSitesFactory } from '@services/sites-factory';
|
||||
import { CoreMainMenuProvider } from '@features/mainmenu/services/mainmenu';
|
||||
|
||||
/**
|
||||
* Service to handle push notifications.
|
||||
|
@ -101,6 +102,10 @@ export class CorePushNotificationsProvider {
|
|||
}
|
||||
});
|
||||
|
||||
CoreEvents.on(CoreMainMenuProvider.MAIN_MENU_HANDLER_BADGE_UPDATED, (data) => {
|
||||
this.updateAddonCounter(data.handler, data.value, data.siteId);
|
||||
});
|
||||
|
||||
// Listen for local notification clicks (generated by the app).
|
||||
CoreLocalNotifications.registerClick<CorePushNotificationsNotificationBasicData>(
|
||||
CorePushNotificationsProvider.COMPONENT,
|
||||
|
|
|
@ -97,10 +97,6 @@
|
|||
--color: var(--core-bottom-tabs-color);
|
||||
--color-selected: var(--core-bottom-tabs-color-selected);
|
||||
--background-selected: var(--core-bottom-tabs-background-selected);
|
||||
ion-badge {
|
||||
--background: var(--core-bottom-tabs-badge-color);
|
||||
--color: var(--core-bottom-tabs-badge-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
--core-link-color: var(--blue);
|
||||
|
|
Loading…
Reference in New Issue