TF-4385 Fix spam banner show it once per day

This commit is contained in:
dab246
2026-03-25 15:43:33 +07:00
committed by Dat H. Pham
parent cee2dd9d14
commit e4107c7363
11 changed files with 54 additions and 44 deletions
@@ -284,7 +284,7 @@ class MailboxController extends BaseMailboxController
mailboxDashBoardController.updateRefreshAllMailboxState(Right(RefreshAllMailboxSuccess())); mailboxDashBoardController.updateRefreshAllMailboxState(Right(RefreshAllMailboxSuccess()));
_handleCreateDefaultFolderIfMissing(mailboxDashBoardController.mapDefaultMailboxIdByRole); _handleCreateDefaultFolderIfMissing(mailboxDashBoardController.mapDefaultMailboxIdByRole);
_handleDataFromNavigationRouter(); _handleDataFromNavigationRouter();
mailboxDashBoardController.getSpamReportBanner(); mailboxDashBoardController.refreshSpamReportBanner();
if (PlatformInfo.isIOS) { if (PlatformInfo.isIOS) {
_updateMailboxIdsBlockNotificationToKeychain(success.mailboxList); _updateMailboxIdsBlockNotificationToKeychain(success.mailboxList);
} }
@@ -10,7 +10,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/domain/model/unread_spa
abstract class SpamReportDataSource { abstract class SpamReportDataSource {
Future<void> storeLastTimeDismissedSpamReported(DateTime lastTimeDismissedSpamReported); Future<void> storeLastTimeDismissedSpamReported(DateTime lastTimeDismissedSpamReported);
Future<DateTime> getLastTimeDismissedSpamReported(); Future<int> getLastTimeDismissedSpamReportedMilliseconds();
Future<void> deleteLastTimeDismissedSpamReported(); Future<void> deleteLastTimeDismissedSpamReported();
@@ -28,7 +28,7 @@ class HiveSpamReportDataSourceImpl extends SpamReportDataSource {
} }
@override @override
Future<DateTime> getLastTimeDismissedSpamReported() { Future<int> getLastTimeDismissedSpamReportedMilliseconds() {
throw UnimplementedError(); throw UnimplementedError();
} }
@@ -21,12 +21,10 @@ class LocalSpamReportDataSourceImpl extends SpamReportDataSource {
); );
@override @override
Future<DateTime> getLastTimeDismissedSpamReported() async { Future<int> getLastTimeDismissedSpamReportedMilliseconds() async {
return Future.sync(() async { return Future.sync(() async {
final spamReportConfig = await _preferencesSettingManager.getSpamReportConfig(); final spamReportConfig = await _preferencesSettingManager.getSpamReportConfig();
return DateTime.fromMillisecondsSinceEpoch( return spamReportConfig.lastTimeDismissedMilliseconds;
spamReportConfig.lastTimeDismissedMilliseconds,
);
}).catchError(_exceptionThrower.throwException); }).catchError(_exceptionThrower.throwException);
} }
@@ -12,8 +12,8 @@ class SpamReportRepositoryImpl extends SpamReportRepository {
SpamReportRepositoryImpl(this.mapDataSource); SpamReportRepositoryImpl(this.mapDataSource);
@override @override
Future<DateTime> getLastTimeDismissedSpamReported() async { Future<int> getLastTimeDismissedSpamReportedMilliseconds() async {
return await mapDataSource[DataSourceType.local]!.getLastTimeDismissedSpamReported(); return await mapDataSource[DataSourceType.local]!.getLastTimeDismissedSpamReportedMilliseconds();
} }
@override @override
@@ -1,10 +1,10 @@
import 'package:core/domain/exceptions/app_base_exception.dart'; import 'package:core/domain/exceptions/app_base_exception.dart';
class NotFoundLastTimeDismissedSpamReportException extends AppBaseException { class SpamDismissCooldownActiveException extends AppBaseException {
NotFoundLastTimeDismissedSpamReportException([super.message]); SpamDismissCooldownActiveException([super.message]);
@override @override
String get exceptionName => 'NotFoundLastTimeDismissedSpamReportException'; String get exceptionName => 'SpamDismissCooldownActiveException';
} }
class NotFoundSpamMailboxCachedException extends AppBaseException { class NotFoundSpamMailboxCachedException extends AppBaseException {
@@ -20,3 +20,10 @@ class NotFoundSpamMailboxException extends AppBaseException {
@override @override
String get exceptionName => 'NotFoundSpamMailboxException'; String get exceptionName => 'NotFoundSpamMailboxException';
} }
class NoUnreadSpamEmailsException extends AppBaseException {
NoUnreadSpamEmailsException([super.message]);
@override
String get exceptionName => 'NoUnreadSpamEmailsException';
}
@@ -6,7 +6,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/domain/model/spam_repor
abstract class SpamReportRepository { abstract class SpamReportRepository {
Future<void> storeLastTimeDismissedSpamReported(DateTime lastTimeDismissedSpamReported); Future<void> storeLastTimeDismissedSpamReported(DateTime lastTimeDismissedSpamReported);
Future<DateTime> getLastTimeDismissedSpamReported(); Future<int> getLastTimeDismissedSpamReportedMilliseconds();
Future<void> deleteLastTimeDismissedSpamReported(); Future<void> deleteLastTimeDismissedSpamReported();
@@ -18,5 +18,3 @@ class GetSpamMailboxCachedFailure extends FeatureFailure {
GetSpamMailboxCachedFailure(exception) : super(exception: exception); GetSpamMailboxCachedFailure(exception) : super(exception: exception);
} }
class InvalidSpamReportCondition extends FeatureFailure {}
@@ -1,15 +1,15 @@
import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart'; import 'package:core/presentation/state/success.dart';
import 'package:core/utils/app_logger.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/exceptions/spam_report_exception.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/repository/spam_report_repository.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/repository/spam_report_repository.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/get_spam_mailbox_cached_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/get_spam_mailbox_cached_state.dart';
class GetSpamMailboxCachedInteractor { class GetSpamMailboxCachedInteractor {
static const int spamReportBannerDisplayIntervalInHour = 12; static const int spamReportBannerDisplayIntervalInHours = 24;
final SpamReportRepository _spamReportRepository; final SpamReportRepository _spamReportRepository;
@@ -19,15 +19,19 @@ class GetSpamMailboxCachedInteractor {
try { try {
yield Right<Failure, Success>(GetSpamMailboxCachedLoading()); yield Right<Failure, Success>(GetSpamMailboxCachedLoading());
if (await _validateIntervalToShowBanner()) { if (await _validateIntervalToShowBanner()) {
final spamMailbox = await _spamReportRepository.getSpamMailboxCached(accountId, userName); final spamMailbox = await _spamReportRepository.getSpamMailboxCached(accountId, userName);
final countUnreadSpamMailbox = spamMailbox.unreadEmails?.value.value.toInt() ?? 0; final countUnreadSpamMailbox = spamMailbox.unreadEmails?.value.value.toInt() ?? 0;
if (countUnreadSpamMailbox > 0) { if (countUnreadSpamMailbox > 0) {
yield Right<Failure, Success>(GetSpamMailboxCachedSuccess(spamMailbox)); yield Right<Failure, Success>(GetSpamMailboxCachedSuccess(spamMailbox));
} else { } else {
yield Left<Failure, Success>(InvalidSpamReportCondition()); yield Left<Failure, Success>(
GetSpamMailboxCachedFailure(NoUnreadSpamEmailsException()),
);
} }
} else { } else {
yield Left<Failure, Success>(InvalidSpamReportCondition()); yield Left<Failure, Success>(
GetSpamMailboxCachedFailure(SpamDismissCooldownActiveException()),
);
} }
} catch (e) { } catch (e) {
yield Left<Failure, Success>(GetSpamMailboxCachedFailure(e)); yield Left<Failure, Success>(GetSpamMailboxCachedFailure(e));
@@ -35,9 +39,19 @@ class GetSpamMailboxCachedInteractor {
} }
Future<bool> _validateIntervalToShowBanner() async { Future<bool> _validateIntervalToShowBanner() async {
final lastTimeDismissedSpamReported = await _spamReportRepository.getLastTimeDismissedSpamReported(); final lastTimeDismissedMs = await _spamReportRepository
final currentTime = DateTime.now().difference(lastTimeDismissedSpamReported); .getLastTimeDismissedSpamReportedMilliseconds();
log('GetSpamMailboxCachedInteractor::_compareSpamReportTime:lastTimeDismissedSpamReported: $lastTimeDismissedSpamReported | currentTime: $currentTime');
return currentTime.inHours > spamReportBannerDisplayIntervalInHour; if (lastTimeDismissedMs <= 0) {
return true;
}
final lastTime = DateTime.fromMillisecondsSinceEpoch(lastTimeDismissedMs);
final now = DateTime.now();
final elapsed = now.difference(lastTime);
final isIntervalElapsed =
elapsed.inHours > spamReportBannerDisplayIntervalInHours;
return isIntervalElapsed;
} }
} }
@@ -2534,24 +2534,6 @@ class MailboxDashBoardController extends ReloadableController
bool get enableSpamReport => spamReportController.enableSpamReport; bool get enableSpamReport => spamReportController.enableSpamReport;
void getSpamReportBanner() {
if (enableSpamReport) {
final spamId = spamMailboxId;
if (spamId == null) {
spamReportController.setSpamPresentationMailbox(null);
return;
}
final spamMailbox = mapMailboxById[spamId];
final unreadEmails = spamMailbox?.unreadEmails?.value.value ?? 0;
if (unreadEmails > 0) {
spamReportController.setSpamPresentationMailbox(spamMailbox);
} else {
spamReportController.setSpamPresentationMailbox(null);
}
}
}
void refreshSpamReportBanner() { void refreshSpamReportBanner() {
if (enableSpamReport && sessionCurrent != null && accountId.value != null) { if (enableSpamReport && sessionCurrent != null && accountId.value != null) {
spamReportController.getSpamMailboxCached(accountId.value!, sessionCurrent!.username); spamReportController.getSpamMailboxCached(accountId.value!, sessionCurrent!.username);
@@ -9,6 +9,7 @@ import 'package:model/extensions/presentation_mailbox_extension.dart';
import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:tmail_ui_user/features/base/base_controller.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/extensions/presentation_mailbox_extension.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/extensions/presentation_mailbox_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/exceptions/spam_report_exception.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/model/spam_report_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/model/spam_report_state.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/get_spam_mailbox_cached_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/get_spam_mailbox_cached_state.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/get_spam_report_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/get_spam_report_state.dart';
@@ -75,7 +76,7 @@ class SpamReportController extends BaseController {
@override @override
void handleFailureViewState(Failure failure) { void handleFailureViewState(Failure failure) {
if (failure is GetSpamMailboxCachedFailure) { if (failure is GetSpamMailboxCachedFailure) {
presentationSpamMailbox.value = null; _validateSpamMailboxChanged(failure);
} else if (failure is GetSpamReportStateFailure) { } else if (failure is GetSpamReportStateFailure) {
_spamReportLoaderStatus = LoaderStatus.completed; _spamReportLoaderStatus = LoaderStatus.completed;
} else { } else {
@@ -147,6 +148,16 @@ class SpamReportController extends BaseController {
presentationSpamMailbox.value = spamMailbox; presentationSpamMailbox.value = spamMailbox;
} }
void _validateSpamMailboxChanged(GetSpamMailboxCachedFailure failure) {
if (failure.exception is NoUnreadSpamEmailsException) {
final currentSpamMailbox = presentationSpamMailbox.value;
if (currentSpamMailbox != null && currentSpamMailbox.countUnreadEmails > 0) {
_storeLastTimeDismissedSpamReportedAction();
}
}
setSpamPresentationMailbox(null);
}
@override @override
void onClose() { void onClose() {
_appLifecycleListener?.dispose(); _appLifecycleListener?.dispose();