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