Skip to content

Commit

Permalink
TF-2871 Block notification on folders Sent, Outbox, Drafts, `Sp…
Browse files Browse the repository at this point in the history
…am` and `Trash`
  • Loading branch information
dab246 committed Jul 17, 2024
1 parent 6b764e8 commit 20c1f1c
Show file tree
Hide file tree
Showing 20 changed files with 253 additions and 81 deletions.
2 changes: 1 addition & 1 deletion ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ SPEC CHECKSUMS:
FirebaseInstallations: 558b1da7d65afeb996fd5c814332f013234ece4e
FirebaseMessaging: e345b219fd15d325f0cf2fef28cb8ce00d851b3f
fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_appauth: 1ce438877bc111c5d8f42da47729909290624886
flutter_downloader: b7301ae057deadd4b1650dc7c05375f10ff12c39
flutter_image_compress_common: ec1d45c362c9d30a3f6a0426c297f47c52007e3e
Expand Down
18 changes: 16 additions & 2 deletions ios/TwakeCore/Jmap/JmapClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ class JmapClient {
accountId: String,
onComplete: @escaping ([Email], [Error]) -> Void) {
guard hasMoreChanges, let sinceState = currentSinceState else {
if !self.totalListEmails.isEmpty {
let sortedListEmails = self.sortListEmails(currentListEmails: self.totalListEmails)
self.totalListEmails = sortedListEmails
}

return onComplete(self.totalListEmails, self.listErrors)
}

Expand All @@ -85,8 +90,7 @@ class JmapClient {
if let response = data.parsing(methodName: JmapConstants.EMAIL_GET_METHOD_NAME, methodCallId: "c1") {
if let listEmail = response.list,
!listEmail.isEmpty {
let sortedListEmails = self.sortListEmails(currentListEmails: listEmail)
self.totalListEmails.append(contentsOf: sortedListEmails)
self.totalListEmails.append(contentsOf: listEmail)
}
self.hasMoreChanges = response.hasMoreChanges ?? false
self.currentSinceState = response.newState
Expand All @@ -97,6 +101,11 @@ class JmapClient {
self.hasMoreChanges = false
self.currentSinceState = nil

if !self.totalListEmails.isEmpty {
let sortedListEmails = self.sortListEmails(currentListEmails: self.totalListEmails)
self.totalListEmails = sortedListEmails
}

onComplete(self.totalListEmails, self.listErrors)
}
},
Expand All @@ -105,6 +114,11 @@ class JmapClient {
self.hasMoreChanges = false
self.currentSinceState = nil

if !self.totalListEmails.isEmpty {
let sortedListEmails = self.sortListEmails(currentListEmails: self.totalListEmails)
self.totalListEmails = sortedListEmails
}

onComplete(self.totalListEmails, self.listErrors)
}
)
Expand Down
1 change: 1 addition & 0 deletions ios/TwakeCore/Jmap/Model/Email/Email.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ struct Email: Codable {
let preview: String?
let from: [EmailAddress]?
let receivedAt: String?
let mailboxIds: [String: Bool]?

func getSenderName() -> String? {
if (from == nil || from?.isEmpty == true) {
Expand Down
3 changes: 2 additions & 1 deletion ios/TwakeCore/Jmap/Utils/JmapConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ class JmapConstants {
"subject",
"preview",
"from",
"receivedAt"
"receivedAt",
"mailboxIds"
]

static let EMAIL_ID = "email_id"
Expand Down
7 changes: 5 additions & 2 deletions ios/TwakeMailNSE/Model/KeychainSharingSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ struct KeychainSharingSession: Codable {
let basicAuth: String?
let tokenEndpoint: String?
let oidcScopes: [String]?
let mailboxIdsBlockNotification: [String]?
}

extension KeychainSharingSession {
Expand All @@ -25,7 +26,8 @@ extension KeychainSharingSession {
tokenOIDC: self.tokenOIDC,
basicAuth: self.basicAuth,
tokenEndpoint: self.tokenEndpoint,
oidcScopes: self.oidcScopes
oidcScopes: self.oidcScopes,
mailboxIdsBlockNotification: self.mailboxIdsBlockNotification
)
}

Expand All @@ -45,7 +47,8 @@ extension KeychainSharingSession {
),
basicAuth: self.basicAuth,
tokenEndpoint: self.tokenEndpoint,
oidcScopes: self.oidcScopes
oidcScopes: self.oidcScopes,
mailboxIdsBlockNotification: self.mailboxIdsBlockNotification
)
}

Expand Down
66 changes: 41 additions & 25 deletions ios/TwakeMailNSE/NotificationService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,31 +85,15 @@ class NotificationService: UNNotificationServiceExtension {
newEmailDeliveryState: newEmailDeliveryState
)

if (emails.count > 1) {
for email in emails {
if (email.id == emails.last?.id) {
self.showModifiedNotification(title: email.getSenderName(),
subtitle: email.subject,
body: email.preview,
badgeCount: emails.count,
userInfo: [JmapConstants.EMAIL_ID : email.id])
return self.notify()
} else {
self.showNewNotification(title: email.getSenderName(),
subtitle: email.subject,
body: email.preview,
badgeCount: emails.count,
notificationId: email.id,
userInfo: [JmapConstants.EMAIL_ID : email.id])
}
}
let mailboxIdsBlockNotification = keychainSharingSession.mailboxIdsBlockNotification ?? []

if (mailboxIdsBlockNotification.isEmpty) {
return self.showListNotification(emails: emails)
} else {
self.showModifiedNotification(title: emails.first!.getSenderName(),
subtitle: emails.first!.subject,
body: emails.first!.preview,
badgeCount: 1,
userInfo: [JmapConstants.EMAIL_ID : emails.first!.id])
return self.notify()
let emailFiltered = self.filterEmailsToPushNotification(
emails: emails,
mailboxIdsBlockNotification: mailboxIdsBlockNotification)
return self.showListNotification(emails: emailFiltered)
}
}
} catch {
Expand All @@ -121,7 +105,39 @@ class NotificationService: UNNotificationServiceExtension {
)
}
}


private func filterEmailsToPushNotification(emails: [Email], mailboxIdsBlockNotification: [String]) -> [Email] {
return emails.filter { email in
guard let mailboxIds = email.mailboxIds else { return true }
for id in mailboxIds.keys {
if mailboxIdsBlockNotification.contains(id) {
return false
}
}
return true
}
}

private func showListNotification(emails: [Email]) {
for email in emails {
if (email.id == emails.last?.id) {
self.showModifiedNotification(title: email.getSenderName(),
subtitle: email.subject,
body: email.preview,
badgeCount: emails.count,
userInfo: [JmapConstants.EMAIL_ID : email.id])
return self.notify()
} else {
self.showNewNotification(title: email.getSenderName(),
subtitle: email.subject,
body: email.preview,
badgeCount: emails.count,
notificationId: email.id,
userInfo: [JmapConstants.EMAIL_ID : email.id])
}
}
}

private func showDefaultNotification(message: String) {
self.modifiedContent?.title = InfoPlistReader(bundle: .app).bundleDisplayName
self.modifiedContent?.body = message
Expand Down
60 changes: 43 additions & 17 deletions lib/features/base/reloadable/reloadable_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,17 @@ abstract class ReloadableController extends BaseController {
void handleFailureViewState(Failure failure) {
if (failure is GetCredentialFailure ||
failure is GetStoredTokenOidcFailure ||
failure is GetAuthenticatedAccountFailure ||
failure is UpdateAccountCacheFailure) {
failure is GetAuthenticatedAccountFailure) {
logError('$runtimeType::handleFailureViewState():Failure = $failure');
goToLogin();
} else if (failure is GetSessionFailure) {
logError('$runtimeType::handleFailureViewState():Failure = $failure');
_handleGetSessionFailure(failure.exception);
} else if (failure is UpdateAccountCacheFailure) {
logError('$runtimeType::handleFailureViewState():Failure = $failure');
_handleUpdateAccountCacheCompleted(
session: failure.session,
apiUrl: failure.apiUrl);
} else {
super.handleFailureViewState(failure);
}
Expand All @@ -50,25 +54,20 @@ abstract class ReloadableController extends BaseController {
void handleSuccessViewState(Success success) {
if (success is GetCredentialViewState) {
log('$runtimeType::handleSuccessViewState:Success = ${success.runtimeType}');
_setDataToInterceptors(
baseUrl: success.baseUrl.origin,
userName: success.userName,
password: success.password);
getSessionAction();
_handleGetCredentialSuccess(success);
} else if (success is GetStoredTokenOidcSuccess) {
log('$runtimeType::handleSuccessViewState:Success = ${success.runtimeType}');
_setDataToInterceptors(
baseUrl: success.baseUrl.toString(),
tokenOIDC: success.tokenOidc,
oidcConfiguration: success.oidcConfiguration);
getSessionAction();
_handleGetStoredTokenOidcSuccess(success);
} else if (success is GetSessionSuccess) {
log('$runtimeType::handleSuccessViewState:Success = ${success.runtimeType}');
updateAccountCache(success.session);
updateAccountCache(
session: success.session,
baseUrl: dynamicUrlInterceptors.baseUrl);
} else if (success is UpdateAccountCacheSuccess) {
log('$runtimeType::handleSuccessViewState:Success = ${success.runtimeType}');
dynamicUrlInterceptors.changeBaseUrl(success.apiUrl);
handleReloaded(success.session);
_handleUpdateAccountCacheCompleted(
session: success.session,
apiUrl: success.apiUrl);
} else {
super.handleSuccessViewState(success);
}
Expand All @@ -85,6 +84,27 @@ abstract class ReloadableController extends BaseController {
consumeState(_getAuthenticatedAccountInteractor.execute());
}

void _handleGetCredentialSuccess(GetCredentialViewState success) {
_setDataToInterceptors(
baseUrl: success.baseUrl.origin,
userName: success.userName,
password: success.password);
getSessionAction();
}

void _handleGetStoredTokenOidcSuccess(GetStoredTokenOidcSuccess success) {
_setDataToInterceptors(
baseUrl: success.baseUrl.toString(),
tokenOIDC: success.tokenOidc,
oidcConfiguration: success.oidcConfiguration);
getSessionAction();
}

void _handleUpdateAccountCacheCompleted({required Session session, String? apiUrl}) {
dynamicUrlInterceptors.changeBaseUrl(apiUrl);
handleReloaded(session);
}

void _setDataToInterceptors({
required String baseUrl,
UserName? userName,
Expand Down Expand Up @@ -131,7 +151,13 @@ abstract class ReloadableController extends BaseController {
}
}

void updateAccountCache(Session session) {
consumeState(_updateAccountCacheInteractor.execute(session));
void updateAccountCache({
required Session session,
String? baseUrl
}) {
consumeState(_updateAccountCacheInteractor.execute(
session: session,
baseUrl: baseUrl
));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ class AuthorizationInterceptors extends QueuedInterceptorsWrapper {
_token = newToken;
_configOIDC = newConfig;
_authenticationType = AuthenticationType.oidc;
log('AuthorizationInterceptors::setTokenAndAuthorityOidc: TokenId = ${newToken?.tokenIdHash}');
log('AuthorizationInterceptors::setTokenAndAuthorityOidc: INITIAL_TOKEN = ${newToken?.token} | EXPIRED_TIME = ${newToken?.expiredTime}');
}

void _updateNewToken(TokenOIDC newToken) {
log('AuthorizationInterceptors::_updateNewToken: TokenId = ${newToken.tokenIdHash}');
log('AuthorizationInterceptors::_updateNewToken: NEW_TOKEN = ${newToken.token} | EXPIRED_TIME = ${newToken.expiredTime}');
_token = newToken;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ class UpdateAccountCacheSuccess extends UIState {
}

class UpdateAccountCacheFailure extends FeatureFailure {
final Session session;
final String apiUrl;

UpdateAccountCacheFailure(dynamic exception) : super(exception: exception);
UpdateAccountCacheFailure({
required this.session,
required this.apiUrl,
dynamic exception,
}) : super(exception: exception);

@override
List<Object?> get props => [session, apiUrl, ...super.props];
}
Original file line number Diff line number Diff line change
@@ -1,39 +1,26 @@
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:core/utils/app_logger.dart';
import 'package:dartz/dartz.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:model/account/authentication_type.dart';
import 'package:model/account/personal_account.dart';
import 'package:model/extensions/personal_account_extension.dart';
import 'package:model/extensions/session_extension.dart';
import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart';
import 'package:tmail_ui_user/features/login/domain/repository/account_repository.dart';
import 'package:tmail_ui_user/features/login/domain/repository/credential_repository.dart';
import 'package:tmail_ui_user/features/login/domain/state/update_authentication_account_state.dart';

class UpdateAccountCacheInteractor {
final AccountRepository _accountRepository;
final CredentialRepository _credentialRepository;

UpdateAccountCacheInteractor(
this._accountRepository,
this._credentialRepository);
UpdateAccountCacheInteractor(this._accountRepository);

Stream<Either<Failure, Success>> execute(Session session) async* {
Stream<Either<Failure, Success>> execute({required Session session, String? baseUrl}) async* {
final apiUrl = _getQualifiedApiUrl(session: session, baseUrl: baseUrl);
log('UpdateAccountCacheInteractor::execute: ApiUrl = $apiUrl');
try{
yield Right(UpdatingAccountCache());

final futureValue = await Future.wait([
_credentialRepository.getBaseUrl(),
_accountRepository.getCurrentAccount(),
], eagerError: true);

final baseUrl = futureValue[0] as Uri;
final currentAccount = futureValue[1] as PersonalAccount;
final apiUrl = session.getQualifiedApiUrl(
baseUrl: currentAccount.authenticationType == AuthenticationType.basic
? baseUrl.origin
: baseUrl.toString());
final currentAccount = await _accountRepository.getCurrentAccount();

await _accountRepository.setCurrentAccount(
currentAccount.fromAccount(
Expand All @@ -46,7 +33,20 @@ class UpdateAccountCacheInteractor {
session: session,
apiUrl: apiUrl));
} catch(e) {
yield Left(UpdateAccountCacheFailure(e));
yield Left(UpdateAccountCacheFailure(
session: session,
apiUrl: apiUrl,
exception: e
));
}
}

String _getQualifiedApiUrl({required Session session, String? baseUrl}) {
try {
return session.getQualifiedApiUrl(baseUrl: baseUrl);
} catch (e) {
logError('UpdateAccountCacheInteractor::_getQualifiedApiUrl:Exception = $e');
return '';
}
}
}
Loading

0 comments on commit 20c1f1c

Please sign in to comment.