MOBILE-3881 menu: Add badge on more tab
This commit is contained in:
		
							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,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(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user