TF-2599 Fix notification flood
Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
@@ -269,7 +269,7 @@ abstract class BaseController extends GetxController
|
||||
FcmTokenController.instance.initialBindingInteractor();
|
||||
await FcmReceiver.instance.onInitialFcmListener();
|
||||
if (PlatformInfo.isMobile) {
|
||||
await LocalNotificationManager.instance.setUp();
|
||||
await LocalNotificationManager.instance.setUp(groupId: session.username.value);
|
||||
}
|
||||
} else {
|
||||
throw NotSupportFCMException();
|
||||
|
||||
+4
-2
@@ -1,18 +1,20 @@
|
||||
|
||||
import 'package:core/presentation/state/failure.dart';
|
||||
import 'package:core/presentation/state/success.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/user_name.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||
|
||||
class GetEmailChangesToRemoveNotificationLoading extends UIState {}
|
||||
|
||||
class GetEmailChangesToRemoveNotificationSuccess extends UIState {
|
||||
|
||||
final UserName userName;
|
||||
final List<EmailId> emailIds;
|
||||
|
||||
GetEmailChangesToRemoveNotificationSuccess(this.emailIds);
|
||||
GetEmailChangesToRemoveNotificationSuccess(this.userName, this.emailIds);
|
||||
|
||||
@override
|
||||
List<Object> get props => [emailIds];
|
||||
List<Object> get props => [userName, emailIds];
|
||||
}
|
||||
|
||||
class GetEmailChangesToRemoveNotificationFailure extends FeatureFailure {
|
||||
|
||||
+7
-3
@@ -1,6 +1,7 @@
|
||||
|
||||
import 'package:core/presentation/state/failure.dart';
|
||||
import 'package:core/presentation/state/success.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/user_name.dart';
|
||||
import 'package:model/mailbox/presentation_mailbox.dart';
|
||||
|
||||
class GetMailboxesNotPutNotificationsLoading extends UIState {}
|
||||
@@ -8,14 +9,17 @@ class GetMailboxesNotPutNotificationsLoading extends UIState {}
|
||||
class GetMailboxesNotPutNotificationsSuccess extends UIState {
|
||||
|
||||
final List<PresentationMailbox> mailboxes;
|
||||
final UserName userName;
|
||||
|
||||
GetMailboxesNotPutNotificationsSuccess(this.mailboxes);
|
||||
GetMailboxesNotPutNotificationsSuccess(this.mailboxes, this.userName);
|
||||
|
||||
@override
|
||||
List<Object> get props => [mailboxes];
|
||||
List<Object> get props => [mailboxes, userName];
|
||||
}
|
||||
|
||||
class GetMailboxesNotPutNotificationsFailure extends FeatureFailure {
|
||||
|
||||
GetMailboxesNotPutNotificationsFailure(exception) : super(exception: exception);
|
||||
final UserName userName;
|
||||
|
||||
GetMailboxesNotPutNotificationsFailure(exception, this.userName) : super(exception: exception);
|
||||
}
|
||||
+1
-1
@@ -38,7 +38,7 @@ class GetEmailChangesToRemoveNotificationInteractor {
|
||||
currentState,
|
||||
propertiesCreated: propertiesCreated,
|
||||
propertiesUpdated: propertiesUpdated);
|
||||
yield Right<Failure, Success>(GetEmailChangesToRemoveNotificationSuccess(emailIds));
|
||||
yield Right<Failure, Success>(GetEmailChangesToRemoveNotificationSuccess(session.username, emailIds));
|
||||
} else {
|
||||
yield Left<Failure, Success>(GetEmailChangesToRemoveNotificationFailure(NotFoundEmailStateException()));
|
||||
}
|
||||
|
||||
+2
-2
@@ -15,9 +15,9 @@ class GetMailboxesNotPutNotificationsInteractor {
|
||||
try {
|
||||
yield Right<Failure, Success>(GetMailboxesNotPutNotificationsLoading());
|
||||
final mailboxes = await _fcmRepository.getMailboxesNotPutNotifications(session, accountId);
|
||||
yield Right<Failure, Success>(GetMailboxesNotPutNotificationsSuccess(mailboxes));
|
||||
yield Right<Failure, Success>(GetMailboxesNotPutNotificationsSuccess(mailboxes, session.username));
|
||||
} catch (e) {
|
||||
yield Left<Failure, Success>(GetMailboxesNotPutNotificationsFailure(e));
|
||||
yield Left<Failure, Success>(GetMailboxesNotPutNotificationsFailure(e, session.username));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,15 +173,17 @@ class EmailChangeListener extends ChangeListener {
|
||||
}
|
||||
}
|
||||
|
||||
void _showLocalNotification(PresentationEmail presentationEmail) {
|
||||
final notificationPayload = NotificationPayload(emailId: presentationEmail.id);
|
||||
log('EmailChangeListener::_showLocalNotification():notificationPayload: $notificationPayload');
|
||||
LocalNotificationManager.instance.showPushNotification(
|
||||
Future<void> _showLocalNotification(
|
||||
UserName userName,
|
||||
PresentationEmail presentationEmail
|
||||
) async {
|
||||
await LocalNotificationManager.instance.showPushNotification(
|
||||
id: presentationEmail.id?.id.value ?? '',
|
||||
title: presentationEmail.subject ?? '',
|
||||
message: presentationEmail.preview,
|
||||
emailAddress: presentationEmail.firstFromAddress,
|
||||
payload: notificationPayload.encodeToString,
|
||||
payload: NotificationPayload(emailId: presentationEmail.id).encodeToString,
|
||||
groupId: userName.value
|
||||
);
|
||||
}
|
||||
|
||||
@@ -193,7 +195,10 @@ class EmailChangeListener extends ChangeListener {
|
||||
_getStoredEmailState();
|
||||
} else if (failure is GetMailboxesNotPutNotificationsFailure) {
|
||||
final listEmails = _emailsAvailablePushNotification.toEmailsAvailablePushNotification();
|
||||
_handleLocalPushNotification(listEmails);
|
||||
_handleLocalPushNotification(
|
||||
userName: failure.userName,
|
||||
emailList: listEmails
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,14 +213,23 @@ class EmailChangeListener extends ChangeListener {
|
||||
_storeEmailDeliveryStateAction(success.accountId, success.userName, _newStateEmailDelivery!);
|
||||
|
||||
if (PlatformInfo.isAndroid) {
|
||||
_handleListEmailToPushNotification(success.emailList);
|
||||
_handleListEmailToPushNotification(
|
||||
userName: success.userName,
|
||||
emailList: success.emailList
|
||||
);
|
||||
}
|
||||
} else if (success is GetMailboxesNotPutNotificationsSuccess) {
|
||||
final listEmails = _emailsAvailablePushNotification.toEmailsAvailablePushNotification(
|
||||
mailboxIdsNotPutNotifications: success.mailboxes.mailboxIds);
|
||||
_handleLocalPushNotification(listEmails);
|
||||
_handleLocalPushNotification(
|
||||
userName: success.userName,
|
||||
emailList: listEmails
|
||||
);
|
||||
} else if (success is GetEmailChangesToRemoveNotificationSuccess) {
|
||||
_handleRemoveLocalNotification(success.emailIds);
|
||||
_handleRemoveLocalNotification(
|
||||
userName: success.userName,
|
||||
emailIds: success.emailIds
|
||||
);
|
||||
} else if (success is GetNewReceiveEmailFromNotificationSuccess) {
|
||||
_getListDetailedEmailByIdAction(success.session, success.accountId, success.emailIds);
|
||||
} else if (success is GetDetailedEmailByIdSuccess) {
|
||||
@@ -226,27 +240,39 @@ class EmailChangeListener extends ChangeListener {
|
||||
}
|
||||
}
|
||||
|
||||
void _handleListEmailToPushNotification(List<PresentationEmail> emailList) {
|
||||
void _handleListEmailToPushNotification({
|
||||
required UserName userName,
|
||||
required List<PresentationEmail> emailList
|
||||
}) {
|
||||
_emailsAvailablePushNotification = emailList;
|
||||
if (_getMailboxesNotPutNotificationsInteractor != null && _accountId != null && _session != null) {
|
||||
consumeState(_getMailboxesNotPutNotificationsInteractor!.execute(_session!, _accountId!));
|
||||
} else {
|
||||
final listEmails = _emailsAvailablePushNotification.toEmailsAvailablePushNotification();
|
||||
_handleLocalPushNotification(listEmails);
|
||||
_handleLocalPushNotification(
|
||||
userName: userName,
|
||||
emailList: listEmails
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _handleLocalPushNotification(List<PresentationEmail> emailList) {
|
||||
log('EmailChangeListener::_handleLocalPushNotification():emailList: $emailList');
|
||||
void _handleLocalPushNotification({
|
||||
required UserName userName,
|
||||
required List<PresentationEmail> emailList
|
||||
}) async {
|
||||
log('EmailChangeListener::_handleLocalPushNotification(): EMAIL_LENGTH = ${emailList.length}');
|
||||
if (emailList.isEmpty) {
|
||||
_emailsAvailablePushNotification.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
for (var presentationEmail in emailList) {
|
||||
_showLocalNotification(presentationEmail);
|
||||
await _showLocalNotification(userName, presentationEmail);
|
||||
}
|
||||
|
||||
LocalNotificationManager.instance.groupPushNotification();
|
||||
if (PlatformInfo.isAndroid) {
|
||||
await LocalNotificationManager.instance.groupPushNotification(groupId: userName.value);
|
||||
}
|
||||
|
||||
_emailsAvailablePushNotification.clear();
|
||||
}
|
||||
@@ -272,10 +298,13 @@ class EmailChangeListener extends ChangeListener {
|
||||
}
|
||||
}
|
||||
|
||||
void _handleRemoveLocalNotification(List<EmailId> emailIds) async {
|
||||
void _handleRemoveLocalNotification({
|
||||
required UserName userName,
|
||||
required List<EmailId> emailIds
|
||||
}) async {
|
||||
log('EmailChangeListener::_handleRemoveLocalNotification():emailIds: $emailIds');
|
||||
await Future.wait(emailIds.map((emailId) => LocalNotificationManager.instance.removeNotification(emailId.id.value)));
|
||||
LocalNotificationManager.instance.removeGroupPushNotification();
|
||||
await LocalNotificationManager.instance.removeGroupPushNotification(userName.value);
|
||||
}
|
||||
|
||||
void _getNewReceiveEmailFromNotificationAction(Session? session, AccountId accountId, jmap.State newState) {
|
||||
|
||||
+14
-32
@@ -1,54 +1,36 @@
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
|
||||
class LocalNotificationConfig {
|
||||
static const _groupId = 'team_mail_notification_group_id';
|
||||
static const _groupName = 'team_mail_notification_group_name';
|
||||
static const _groupDescription = 'Twake Mail group notifications';
|
||||
static const _channelId = 'team_mail_notification_channel_id';
|
||||
static const _channelName = 'Twake Mail notifications';
|
||||
static const _channelDescription = 'Twake Mail notifications';
|
||||
static const notificationTitle = 'Twake Mail';
|
||||
static const notificationMessage = 'You have new messages';
|
||||
static const messageHasBeenSentSuccessfully = 'Message has been sent successfully.';
|
||||
static const int groupNotificationId = 1995;
|
||||
static const String NOTIFICATION_CHANNEL = 'New Email';
|
||||
static const int MIN_EMAILS_TO_GROUP = 3;
|
||||
|
||||
static const iosInitializationSettings = DarwinInitializationSettings();
|
||||
|
||||
static const androidInitializationSettings = AndroidInitializationSettings('notification_icon');
|
||||
|
||||
static const androidNotificationChannel = AndroidNotificationChannel(
|
||||
_channelId,
|
||||
_channelName,
|
||||
description: _channelDescription,
|
||||
groupId: _groupId,
|
||||
importance: Importance.max,
|
||||
showBadge: true
|
||||
);
|
||||
|
||||
static const androidNotificationChannelGroup = AndroidNotificationChannelGroup(
|
||||
_groupId,
|
||||
_groupName,
|
||||
description: _groupDescription
|
||||
);
|
||||
|
||||
LocalNotificationConfig._internal();
|
||||
|
||||
static final LocalNotificationConfig _instance = LocalNotificationConfig._internal();
|
||||
|
||||
static LocalNotificationConfig get instance => _instance;
|
||||
|
||||
NotificationDetails generateNotificationDetails({StyleInformation? styleInformation, bool setAsGroup = false}) {
|
||||
NotificationDetails generateNotificationDetails({
|
||||
StyleInformation? styleInformation,
|
||||
bool setAsGroup = false,
|
||||
String? groupId,
|
||||
}) {
|
||||
return NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
androidNotificationChannel.id,
|
||||
androidNotificationChannel.name,
|
||||
channelDescription: androidNotificationChannel.description,
|
||||
groupKey: androidNotificationChannel.groupId,
|
||||
NOTIFICATION_CHANNEL,
|
||||
NOTIFICATION_CHANNEL,
|
||||
groupKey: groupId,
|
||||
visibility: NotificationVisibility.public,
|
||||
importance: Importance.max,
|
||||
priority: Priority.high,
|
||||
setAsGroupSummary: setAsGroup,
|
||||
styleInformation: styleInformation,
|
||||
groupAlertBehavior: setAsGroup
|
||||
? GroupAlertBehavior.summary
|
||||
: GroupAlertBehavior.all,
|
||||
channelShowBadge: true,
|
||||
showWhen: true,
|
||||
largeIcon: const DrawableResourceAndroidBitmap('ic_large_notification')
|
||||
@@ -57,7 +39,7 @@ class LocalNotificationConfig {
|
||||
presentSound: true,
|
||||
presentAlert: true,
|
||||
presentBadge: true,
|
||||
threadIdentifier: _channelId
|
||||
threadIdentifier: NOTIFICATION_CHANNEL
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
+49
-29
@@ -1,4 +1,3 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:core/presentation/extensions/html_extension.dart';
|
||||
@@ -31,14 +30,14 @@ class LocalNotificationManager {
|
||||
|
||||
NotificationAppLaunchDetails? _notificationAppLaunchDetails;
|
||||
|
||||
Future<void> setUp() async {
|
||||
Future<void> setUp({required String groupId}) async {
|
||||
try {
|
||||
final isInitialNotification = await _initLocalNotification();
|
||||
log('LocalNotificationManager::setUp:isInitialNotification: $isInitialNotification');
|
||||
await _checkLocalNotificationPermission();
|
||||
if (PlatformInfo.isAndroid) {
|
||||
await _createAndroidNotificationChannelGroup();
|
||||
await _createAndroidNotificationChannel();
|
||||
await _createAndroidNotificationChannelGroup(groupId);
|
||||
await _createAndroidNotificationChannel(groupId);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('LocalNotificationManager::setUp(): ERROR: ${e.toString()}');
|
||||
@@ -81,10 +80,12 @@ class LocalNotificationManager {
|
||||
Future<void> _checkLocalNotificationPermission() async {
|
||||
if (PlatformInfo.isAndroid) {
|
||||
final granted = await _isAndroidPermissionGranted();
|
||||
log('LocalNotificationManager::requestPermissionAndroid: _isAndroidPermissionGranted = $granted');
|
||||
if (!granted) {
|
||||
await _requestPermissions();
|
||||
}
|
||||
} else if (PlatformInfo.isIOS) {
|
||||
log('LocalNotificationManager::requestPermissionIOS');
|
||||
await _requestPermissions();
|
||||
}
|
||||
}
|
||||
@@ -109,25 +110,33 @@ class LocalNotificationManager {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _createAndroidNotificationChannel() async {
|
||||
Future<void> _createAndroidNotificationChannel(String groupId) async {
|
||||
return await _localNotificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.createNotificationChannel(LocalNotificationConfig.androidNotificationChannel);
|
||||
?.createNotificationChannel(AndroidNotificationChannel(
|
||||
LocalNotificationConfig.NOTIFICATION_CHANNEL,
|
||||
LocalNotificationConfig.NOTIFICATION_CHANNEL,
|
||||
groupId: groupId,
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> _createAndroidNotificationChannelGroup() async {
|
||||
Future<void> _createAndroidNotificationChannelGroup(String groupId) async {
|
||||
await _localNotificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()!
|
||||
.createNotificationChannelGroup(LocalNotificationConfig.androidNotificationChannelGroup);
|
||||
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.createNotificationChannelGroup(AndroidNotificationChannelGroup(
|
||||
groupId,
|
||||
groupId,
|
||||
));
|
||||
}
|
||||
|
||||
void showPushNotification({
|
||||
Future<void> showPushNotification({
|
||||
required String id,
|
||||
required String title,
|
||||
String? message,
|
||||
EmailAddress? emailAddress,
|
||||
String? payload,
|
||||
bool isInboxStyle = true
|
||||
bool isInboxStyle = true,
|
||||
String? groupId,
|
||||
}) async {
|
||||
if (isInboxStyle) {
|
||||
final inboxStyleInformation = InboxStyleInformation(
|
||||
@@ -143,7 +152,10 @@ class LocalNotificationManager {
|
||||
id.hashCode,
|
||||
title,
|
||||
message,
|
||||
LocalNotificationConfig.instance.generateNotificationDetails(styleInformation: inboxStyleInformation),
|
||||
LocalNotificationConfig.instance.generateNotificationDetails(
|
||||
styleInformation: inboxStyleInformation,
|
||||
groupId: groupId
|
||||
),
|
||||
payload: payload
|
||||
);
|
||||
} else {
|
||||
@@ -151,7 +163,10 @@ class LocalNotificationManager {
|
||||
id.hashCode,
|
||||
title,
|
||||
message,
|
||||
LocalNotificationConfig.instance.generateNotificationDetails(styleInformation: const DefaultStyleInformation(true, true)),
|
||||
LocalNotificationConfig.instance.generateNotificationDetails(
|
||||
styleInformation: const DefaultStyleInformation(true, true),
|
||||
groupId: groupId
|
||||
),
|
||||
payload: payload
|
||||
);
|
||||
}
|
||||
@@ -161,44 +176,49 @@ class LocalNotificationManager {
|
||||
return _localNotificationsPlugin.cancel(id.hashCode);
|
||||
}
|
||||
|
||||
void groupPushNotification() async {
|
||||
if (PlatformInfo.isIOS) {
|
||||
return;
|
||||
}
|
||||
|
||||
Future<void> groupPushNotification({required String groupId}) async {
|
||||
final activeNotifications = await _localNotificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.getActiveNotifications();
|
||||
?.getActiveNotifications() ?? [];
|
||||
|
||||
if (activeNotifications != null && activeNotifications.isNotEmpty) {
|
||||
final listActiveNotificationByGroup = activeNotifications
|
||||
.where((notification) => notification.groupKey == groupId)
|
||||
.toList();
|
||||
log('LocalNotificationManager::groupPushNotification(): groupId = $groupId | activeNotifications = ${activeNotifications.length} | listActiveNotificationByGroup = ${listActiveNotificationByGroup.length}');
|
||||
if (listActiveNotificationByGroup.length >= LocalNotificationConfig.MIN_EMAILS_TO_GROUP) {
|
||||
final inboxStyleInformation = InboxStyleInformation(
|
||||
[''],
|
||||
summaryText: currentContext != null
|
||||
? AppLocalizations.of(currentContext!).totalNewMessagePushNotification(activeNotifications.length - 1).addBlockTag('b')
|
||||
: '${activeNotifications.length - 1} new emails'.addBlockTag('b'),
|
||||
? AppLocalizations.of(currentContext!).totalNewMessagePushNotification(listActiveNotificationByGroup.length).addBlockTag('b')
|
||||
: '${listActiveNotificationByGroup.length} new emails'.addBlockTag('b'),
|
||||
htmlFormatSummaryText: true,
|
||||
);
|
||||
|
||||
await _localNotificationsPlugin.show(
|
||||
LocalNotificationConfig.groupNotificationId,
|
||||
groupId.hashCode,
|
||||
null,
|
||||
null,
|
||||
LocalNotificationConfig.instance.generateNotificationDetails(
|
||||
setAsGroup: true,
|
||||
styleInformation: inboxStyleInformation
|
||||
styleInformation: inboxStyleInformation,
|
||||
groupId: groupId
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void removeGroupPushNotification() async {
|
||||
Future<void> removeGroupPushNotification(String groupId) async {
|
||||
final activeNotifications = await _localNotificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.getActiveNotifications();
|
||||
log('LocalNotificationManager::removeGroupPushNotification():activeNotifications: ${activeNotifications?.length}');
|
||||
if (activeNotifications == null || activeNotifications.length <= 1) {
|
||||
?.getActiveNotifications() ?? [];
|
||||
|
||||
final listActiveNotificationByGroup = activeNotifications
|
||||
.where((notification) => notification.groupKey == groupId)
|
||||
.toList();
|
||||
log('LocalNotificationManager::removeGroupPushNotification(): activeNotifications = ${activeNotifications.length} | listActiveNotificationByGroup = ${listActiveNotificationByGroup.length}');
|
||||
if (listActiveNotificationByGroup.length <= 1) {
|
||||
log('LocalNotificationManager::groupPushNotification():canceled');
|
||||
await _localNotificationsPlugin.cancel(LocalNotificationConfig.groupNotificationId);
|
||||
await _localNotificationsPlugin.cancel(groupId.hashCode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user