1/* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17import {Inject, Injectable, NgZone} from '@angular/core'; 18import {MatSnackBar} from '@angular/material/snack-bar'; 19import {assertDefined} from 'common/assert_utils'; 20import {NotificationType, UserNotification} from 'messaging/user_notification'; 21import {UserNotificationsListener} from 'messaging/user_notifications_listener'; 22import {SnackBarComponent} from './snack_bar_component'; 23 24@Injectable({providedIn: 'root'}) 25export class SnackBarOpener implements UserNotificationsListener { 26 constructor( 27 @Inject(NgZone) private ngZone: NgZone, 28 @Inject(MatSnackBar) private snackBar: MatSnackBar, 29 ) {} 30 31 onNotifications(notifications: UserNotification[]) { 32 const messages = this.convertNotificationsToMessages(notifications); 33 34 if (messages.length === 0) { 35 return; 36 } 37 38 this.ngZone.run(() => { 39 // The snackbar needs to be opened within ngZone, 40 // otherwise it will first display on the left and then will jump to the center 41 this.snackBar.openFromComponent(SnackBarComponent, { 42 data: messages, 43 duration: 10000, 44 }); 45 }); 46 } 47 48 private convertNotificationsToMessages( 49 notifications: UserNotification[], 50 ): string[] { 51 const messages: string[] = []; 52 53 const warnings = notifications.filter( 54 (n) => n.getNotificationType() === NotificationType.WARNING, 55 ); 56 const groups = this.groupNotificationsByDescriptor(warnings); 57 58 for (const groupedWarnings of groups) { 59 const CROP_THRESHOLD = 5; 60 const countUsed = Math.min(groupedWarnings.length, CROP_THRESHOLD); 61 const countCropped = groupedWarnings.length - countUsed; 62 63 groupedWarnings.slice(0, countUsed).forEach((warning) => { 64 messages.push(warning.getMessage()); 65 }); 66 67 if (countCropped > 0) { 68 messages.push( 69 `... (cropped ${countCropped} '${groupedWarnings[0].getDescriptor()}' messages)`, 70 ); 71 } 72 } 73 74 return messages; 75 } 76 77 private groupNotificationsByDescriptor( 78 warnings: UserNotification[], 79 ): Set<UserNotification[]> { 80 const groups = new Map<string, UserNotification[]>(); 81 82 warnings.forEach((warning) => { 83 if (groups.get(warning.getDescriptor()) === undefined) { 84 groups.set(warning.getDescriptor(), []); 85 } 86 assertDefined(groups.get(warning.getDescriptor())).push(warning); 87 }); 88 89 return new Set(groups.values()); 90 } 91} 92