diff --git a/src/addons/messages/services/handlers/mainmenu.ts b/src/addons/messages/services/handlers/mainmenu.ts index 7b19afaa5..564f844ba 100644 --- a/src/addons/messages/services/handlers/mainmenu.ts +++ b/src/addons/messages/services/handlers/mainmenu.ts @@ -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, + ); } /** diff --git a/src/addons/notifications/services/handlers/mainmenu.ts b/src/addons/notifications/services/handlers/mainmenu.ts index a19caaae9..bd4800729 100644 --- a/src/addons/notifications/services/handlers/mainmenu.ts +++ b/src/addons/notifications/services/handlers/mainmenu.ts @@ -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 { diff --git a/src/core/features/mainmenu/pages/menu/menu.html b/src/core/features/mainmenu/pages/menu/menu.html index 18a5bb380..b7a79fa7f 100644 --- a/src/core/features/mainmenu/pages/menu/menu.html +++ b/src/core/features/mainmenu/pages/menu/menu.html @@ -9,9 +9,9 @@ - + - + {{ tab.title | translate }} {{ tab.badgeA11yText | translate: {$a : tab.badge } }} @@ -20,9 +20,10 @@ - + {{ 'core.more' | translate }} + diff --git a/src/core/features/mainmenu/pages/menu/menu.scss b/src/core/features/mainmenu/pages/menu/menu.scss index b359d98f0..993e0cea6 100644 --- a/src/core/features/mainmenu/pages/menu/menu.scss +++ b/src/core/features/mainmenu/pages/menu/menu.scss @@ -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; } diff --git a/src/core/features/mainmenu/pages/menu/menu.ts b/src/core/features/mainmenu/pages/menu/menu.ts index 36d929f4d..63799d858 100644 --- a/src/core/features/mainmenu/pages/menu/menu.ts +++ b/src/core/features/mainmenu/pages/menu/menu.ts @@ -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,34 +139,52 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { * Init handlers on change (size or handlers). */ initHandlers(): void { - if (this.allHandlers) { - this.tabsPlacement = CoreMainMenu.getTabPlacement(); - - const handlers = this.allHandlers.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[] = []; - - for (let i = 0; i < handlers.length; i++) { - const handler = handlers[i]; - - // Check if the handler is already in the tabs list. If so, use it. - const tab = this.tabs.find((tab) => tab.page == handler.page); - - tab ? tab.hide = false : null; - handler.hide = false; - handler.id = handler.id || 'core-mainmenu-' + CoreUtils.getUniqueId('CoreMainMenuPage'); - - newTabs.push(tab || handler); - } - - this.tabs = newTabs; - - // Sort them by priority so new handlers are in the right position. - this.tabs.sort((a, b) => (b.priority || 0) - (a.priority || 0)); - - this.loaded = CoreMainMenuDelegate.areHandlersLoaded(); + if (!this.allHandlers) { + return; } + this.tabsPlacement = CoreMainMenu.getTabPlacement(); + + 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[] = []; + + for (let i = 0; i < handlers.length; i++) { + const handler = handlers[i]; + + // Check if the handler is already in the tabs list. If so, use it. + const tab = this.tabs.find((tab) => tab.page == handler.page); + + tab ? tab.hide = false : null; + handler.hide = false; + handler.id = handler.id || 'core-mainmenu-' + CoreUtils.getUniqueId('CoreMainMenuPage'); + + newTabs.push(tab || handler); + } + + this.tabs = newTabs; + + // 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(); } /** diff --git a/src/core/features/mainmenu/services/mainmenu.ts b/src/core/features/mainmenu/services/mainmenu.ts index 683f8aea7..d34ecde37 100644 --- a/src/core/features/mainmenu/services/mainmenu.ts +++ b/src/core/features/mainmenu/services/mainmenu.ts @@ -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; + +export type CoreMainMenuHandlerBadgeUpdatedEventData = { + handler: string; // Handler name. + value: number; // New counter value. +}; diff --git a/src/core/features/pushnotifications/services/pushnotifications.ts b/src/core/features/pushnotifications/services/pushnotifications.ts index 6b02caf7e..9de4396d8 100644 --- a/src/core/features/pushnotifications/services/pushnotifications.ts +++ b/src/core/features/pushnotifications/services/pushnotifications.ts @@ -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( CorePushNotificationsProvider.COMPONENT, diff --git a/src/theme/theme.light.scss b/src/theme/theme.light.scss index fb0ad53ec..56998e1eb 100644 --- a/src/theme/theme.light.scss +++ b/src/theme/theme.light.scss @@ -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);