Skip to content

Commit ae29d9c

Browse files
committed
feat(notifications): dismiss AuthenticationErrorNotification when authentication issue is solved
1 parent 7f2c229 commit ae29d9c

File tree

7 files changed

+98
-35
lines changed

7 files changed

+98
-35
lines changed

feature/notification/api/src/commonMain/composeResources/values/strings.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
<string name="notification_notify_error_text">An error has occurred while trying to create a system notification for a new message. The reason is most likely a missing notification sound.\n\nTap to open notification settings.</string>
1616

1717
<string name="notification_authentication_error_title">Authentication failed</string>
18-
<string name="notification_authentication_error_text">Authentication failed for %1$s. Update your server settings.</string>
18+
<string name="notification_authentication_incoming_server_error_text">Authentication failed for %1$s. Update your incoming server settings.</string>
19+
<string name="notification_authentication_outgoing_server_error_text">Authentication failed for %1$s. Update your outgoing server settings.</string>
1920

2021
<string name="notification_certificate_error_public">Certificate error</string>
2122
<string name="notification_certificate_error_title">Certificate error for %1$s</string>

feature/notification/api/src/commonMain/kotlin/net/thunderbird/feature/notification/api/content/AuthenticationErrorNotification.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ import net.thunderbird.feature.notification.api.ui.icon.NotificationIcon
88
import net.thunderbird.feature.notification.api.ui.icon.NotificationIcons
99
import net.thunderbird.feature.notification.api.ui.style.inAppNotificationStyle
1010
import net.thunderbird.feature.notification.resources.api.Res
11-
import net.thunderbird.feature.notification.resources.api.notification_authentication_error_text
1211
import net.thunderbird.feature.notification.resources.api.notification_authentication_error_title
12+
import net.thunderbird.feature.notification.resources.api.notification_authentication_incoming_server_error_text
13+
import net.thunderbird.feature.notification.resources.api.notification_authentication_outgoing_server_error_text
1314
import org.jetbrains.compose.resources.getString
1415

1516
/**
@@ -25,8 +26,8 @@ data class AuthenticationErrorNotification private constructor(
2526
override val title: String,
2627
override val contentText: String?,
2728
override val channel: NotificationChannel,
28-
override val icon: NotificationIcon = NotificationIcons.AuthenticationError,
2929
) : AppNotification(), SystemNotification, InAppNotification {
30+
override val icon: NotificationIcon = NotificationIcons.AuthenticationError
3031
override val severity: NotificationSeverity = NotificationSeverity.Fatal
3132
override val actions: Set<NotificationAction> = buildSet {
3233
val action = if (isIncomingServerError) {
@@ -63,7 +64,11 @@ data class AuthenticationErrorNotification private constructor(
6364
accountNumber = accountNumber,
6465
title = getString(resource = Res.string.notification_authentication_error_title),
6566
contentText = getString(
66-
resource = Res.string.notification_authentication_error_text,
67+
resource = if (isIncomingServerError) {
68+
Res.string.notification_authentication_incoming_server_error_text
69+
} else {
70+
Res.string.notification_authentication_outgoing_server_error_text
71+
},
6772
accountDisplayName,
6873
),
6974
channel = NotificationChannel.Miscellaneous(accountUuid = accountUuid),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package net.thunderbird.feature.notification.testing.fake
2+
3+
import kotlinx.coroutines.flow.Flow
4+
import kotlinx.coroutines.flow.flow
5+
import net.thunderbird.core.outcome.Outcome
6+
import net.thunderbird.feature.notification.api.NotificationId
7+
import net.thunderbird.feature.notification.api.NotificationManager
8+
import net.thunderbird.feature.notification.api.command.NotificationCommand.Failure
9+
import net.thunderbird.feature.notification.api.command.NotificationCommand.Success
10+
import net.thunderbird.feature.notification.api.content.Notification
11+
12+
open class FakeNotificationManager(
13+
private val emitOnSend: (notification: Notification) -> Outcome<Success<Notification>, Failure<Notification>>,
14+
private val emitOnDismissNotification: (notification: Notification) -> Outcome<
15+
Success<Notification>,
16+
Failure<Notification>,
17+
>,
18+
private val emitOnDismissId: (id: NotificationId) -> Outcome<Success<Notification>, Failure<Notification>>,
19+
) : NotificationManager {
20+
override fun send(notification: Notification): Flow<Outcome<Success<Notification>, Failure<Notification>>> = flow {
21+
emit(emitOnSend(notification))
22+
}
23+
24+
override fun dismiss(id: NotificationId): Flow<Outcome<Success<Notification>, Failure<Notification>>> = flow {
25+
emit(emitOnDismissId(id))
26+
}
27+
28+
override fun dismiss(notification: Notification): Flow<Outcome<Success<Notification>, Failure<Notification>>> =
29+
flow {
30+
emit(emitOnDismissNotification(notification))
31+
}
32+
}

legacy/core/src/main/java/com/fsck/k9/controller/KoinModule.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import com.fsck.k9.notification.NotificationStrategy
1414
import net.thunderbird.core.featureflag.FeatureFlagProvider
1515
import net.thunderbird.core.logging.Logger
1616
import net.thunderbird.feature.mail.folder.api.OutboxFolderManager
17-
import net.thunderbird.feature.notification.api.sender.NotificationSender
17+
import net.thunderbird.feature.notification.api.NotificationManager
1818
import org.koin.core.qualifier.named
1919
import org.koin.dsl.binds
2020
import org.koin.dsl.module
@@ -35,7 +35,7 @@ val controllerModule = module {
3535
get(named("controllerExtensions")),
3636
get<FeatureFlagProvider>(),
3737
get<Logger>(named("syncDebug")),
38-
get<NotificationSender>(),
38+
get<NotificationManager>(),
3939
get<OutboxFolderManager>(),
4040
)
4141
} binds arrayOf(MessagingControllerRegistry::class)

legacy/core/src/main/java/com/fsck/k9/controller/MessagingController.java

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,15 @@
8585
import net.thunderbird.core.android.account.LegacyAccountDto;
8686
import net.thunderbird.core.common.exception.MessagingException;
8787
import net.thunderbird.core.featureflag.FeatureFlagProvider;
88-
import net.thunderbird.core.featureflag.FeatureFlagResult.Enabled;
8988
import net.thunderbird.core.featureflag.compat.FeatureFlagProviderCompat;
9089
import net.thunderbird.core.logging.Logger;
9190
import net.thunderbird.core.logging.legacy.Log;
9291
import net.thunderbird.feature.mail.folder.api.OutboxFolderManager;
9392
import net.thunderbird.feature.mail.folder.api.OutboxFolderManagerKt;
93+
import net.thunderbird.feature.notification.api.NotificationManager;
9494
import net.thunderbird.feature.notification.api.content.AuthenticationErrorNotification;
9595
import net.thunderbird.feature.notification.api.content.NotificationFactoryCoroutineCompat;
96-
import net.thunderbird.feature.notification.api.sender.NotificationSender;
96+
import net.thunderbird.feature.notification.api.dismisser.compat.NotificationDismisserCompat;
9797
import net.thunderbird.feature.notification.api.sender.compat.NotificationSenderCompat;
9898
import net.thunderbird.feature.search.legacy.LocalMessageSearch;
9999
import org.jetbrains.annotations.NotNull;
@@ -145,9 +145,9 @@ public class MessagingController implements MessagingControllerRegistry, Messagi
145145
private final ArchiveOperations archiveOperations;
146146
private final FeatureFlagProvider featureFlagProvider;
147147
private final Logger syncDebugLogger;
148-
private final NotificationSenderCompat notificationSender;
149148
private final OutboxFolderManager outboxFolderManager;
150-
149+
private final NotificationSenderCompat notificationSender;
150+
private final NotificationDismisserCompat notificationDismisser;
151151

152152
private volatile boolean stopped = false;
153153

@@ -171,7 +171,7 @@ public static MessagingController getInstance(Context context) {
171171
List<ControllerExtension> controllerExtensions,
172172
FeatureFlagProvider featureFlagProvider,
173173
Logger syncDebugLogger,
174-
NotificationSender notificationSender,
174+
NotificationManager notificationManager,
175175
OutboxFolderManager outboxFolderManager
176176
) {
177177
this.context = context;
@@ -186,7 +186,8 @@ public static MessagingController getInstance(Context context) {
186186
this.localDeleteOperationDecider = localDeleteOperationDecider;
187187
this.featureFlagProvider = featureFlagProvider;
188188
this.syncDebugLogger = syncDebugLogger;
189-
this.notificationSender = new NotificationSenderCompat(notificationSender);
189+
this.notificationSender = new NotificationSenderCompat(notificationManager);
190+
this.notificationDismisser = new NotificationDismisserCompat(notificationManager);
190191
this.outboxFolderManager = outboxFolderManager;
191192

192193
controllerThread = new Thread(new Runnable() {
@@ -707,32 +708,36 @@ public void handleAuthenticationFailure(LegacyAccountDto account, boolean incomi
707708
migrateAccountToOAuth(account);
708709
}
709710

710-
if (FeatureFlagProviderCompat.provide(featureFlagProvider, "display_in_app_notifications") ==
711-
Enabled.INSTANCE) {
711+
if (FeatureFlagProviderCompat.provide(featureFlagProvider, "display_in_app_notifications").isEnabled()) {
712712
Log.d("handleAuthenticationFailure: sending in-app notification");
713-
final AuthenticationErrorNotification notification = NotificationFactoryCoroutineCompat.create(
714-
continuation ->
715-
AuthenticationErrorNotification.Companion.invoke(
716-
account.getUuid(),
717-
account.getDisplayName(),
718-
account.getAccountNumber(),
719-
incoming,
720-
continuation
721-
)
722-
);
713+
final AuthenticationErrorNotification notification = createAuthenticationErrorNotification(account, incoming);
723714

724715
notificationSender.send(notification, outcome -> {
725716
Log.v("notificationSender outcome = " + outcome);
726717
});
727718
}
728719

729720
if (FeatureFlagProviderCompat.provide(featureFlagProvider,
730-
"use_notification_sender_for_system_notifications") != Enabled.INSTANCE) {
721+
"use_notification_sender_for_system_notifications").isDisabled()) {
731722
Log.d("handleAuthenticationFailure: sending system notification via old notification controller");
732723
notificationController.showAuthenticationErrorNotification(account, incoming);
733724
}
734725
}
735726

727+
private AuthenticationErrorNotification createAuthenticationErrorNotification(
728+
LegacyAccountDto account, boolean incoming) {
729+
return NotificationFactoryCoroutineCompat.create(
730+
continuation ->
731+
AuthenticationErrorNotification.Companion.invoke(
732+
account.getUuid(),
733+
account.getDisplayName(),
734+
account.getAccountNumber(),
735+
incoming,
736+
continuation
737+
)
738+
);
739+
}
740+
736741
private void migrateAccountToOAuth(LegacyAccountDto account) {
737742
account.setIncomingServerSettings(account.getIncomingServerSettings().newAuthenticationType(AuthType.XOAUTH2));
738743
account.setOutgoingServerSettings(account.getOutgoingServerSettings().newAuthenticationType(AuthType.XOAUTH2));
@@ -2599,10 +2604,15 @@ public void checkAuthenticationProblem(LegacyAccountDto account) {
25992604
if (isAuthenticationProblem(account, true)) {
26002605
handleAuthenticationFailure(account, true);
26012606
return;
2607+
} else {
2608+
clearAuthenticationErrorNotification(account, true);
26022609
}
2610+
26032611
// checking outgoing server configuration
26042612
if (isAuthenticationProblem(account, false)) {
26052613
handleAuthenticationFailure(account, false);
2614+
} else {
2615+
clearAuthenticationErrorNotification(account, false);
26062616
}
26072617
}
26082618

@@ -2614,6 +2624,16 @@ private boolean isAuthenticationProblem(LegacyAccountDto account, boolean incomi
26142624
serverSettings.authenticationType == AuthType.XOAUTH2 && account.getOAuthState() == null;
26152625
}
26162626

2627+
private void clearAuthenticationErrorNotification(LegacyAccountDto account, boolean incoming) {
2628+
if (FeatureFlagProviderCompat.provide(featureFlagProvider, "display_in_app_notifications").isEnabled()) {
2629+
final AuthenticationErrorNotification notification = createAuthenticationErrorNotification(
2630+
account, incoming);
2631+
notificationDismisser.dismiss(notification,outcome -> {
2632+
Log.v("notificationDismisser outcome = " + outcome);
2633+
});
2634+
}
2635+
}
2636+
26172637
void actOnMessagesGroupedByAccountAndFolder(List<MessageReference> messages, MessageActor actor) {
26182638
Map<String, Map<Long, List<MessageReference>>> accountMap = groupMessagesByAccountAndFolder(messages);
26192639

@@ -2702,6 +2722,7 @@ public void syncStarted(@NotNull String folderServerId) {
27022722

27032723
@Override
27042724
public void syncAuthenticationSuccess() {
2725+
clearAuthenticationErrorNotification(account, true);
27052726
notificationController.clearAuthenticationErrorNotification(account, true);
27062727
}
27072728

legacy/core/src/test/java/com/fsck/k9/controller/MessagingControllerTest.java

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@
4040
import net.thunderbird.core.logging.Logger;
4141
import net.thunderbird.core.outcome.Outcome;
4242
import net.thunderbird.feature.mail.folder.api.OutboxFolderManager;
43+
import net.thunderbird.feature.notification.api.NotificationManager;
4344
import net.thunderbird.feature.notification.api.sender.NotificationSender;
4445
import net.thunderbird.feature.notification.testing.fake.FakeInAppOnlyNotification;
46+
import net.thunderbird.feature.notification.testing.fake.FakeNotificationManager;
4547
import net.thunderbird.legacy.core.mailstore.folder.FakeOutboxFolderManager;
4648
import org.junit.After;
4749
import org.junit.Before;
@@ -132,9 +134,11 @@ public void setUp() throws MessagingException {
132134
preferences = Preferences.getPreferences();
133135
featureFlagProvider = key -> Disabled.INSTANCE;
134136

135-
final NotificationSender notificationSender = notification ->
136-
(flowCollector, continuation) ->
137-
Outcome.Companion.success(new FakeInAppOnlyNotification());
137+
final NotificationManager notificationManager = new FakeNotificationManager(
138+
notification -> Outcome.Companion.success(new FakeInAppOnlyNotification()),
139+
notification -> Outcome.Companion.success(new FakeInAppOnlyNotification()),
140+
id -> Outcome.Companion.success(new FakeInAppOnlyNotification())
141+
);
138142

139143
final OutboxFolderManager fakeOutboxFolderManager = new FakeOutboxFolderManager(FOLDER_ID);
140144

@@ -152,7 +156,7 @@ public void setUp() throws MessagingException {
152156
Collections.<ControllerExtension>emptyList(),
153157
featureFlagProvider,
154158
syncLogger,
155-
notificationSender,
159+
notificationManager,
156160
fakeOutboxFolderManager
157161
);
158162

@@ -198,7 +202,7 @@ private void setupRemoteSearch() throws Exception {
198202
when(localNewMessage1.getUid()).thenReturn("newMessageUid1");
199203
when(localNewMessage2.getUid()).thenReturn("newMessageUid2");
200204
when(backend.search(eq(FOLDER_NAME), anyString(), nullable(Set.class), nullable(Set.class), eq(false)))
201-
.thenReturn(remoteMessages);
205+
.thenReturn(remoteMessages);
202206
when(localFolder.extractNewMessages(ArgumentMatchers.<String>anyList())).thenReturn(newRemoteMessages);
203207
when(localFolder.getMessage("newMessageUid1")).thenReturn(localNewMessage1);
204208
when(localFolder.getMessage("newMessageUid2")).thenAnswer(
@@ -290,7 +294,7 @@ public void searchRemoteMessagesSynchronous_shouldNotFetchExistingMessages() thr
290294
public void searchRemoteMessagesSynchronous_shouldNotifyOnFailure() throws Exception {
291295
setupRemoteSearch();
292296
when(backend.search(anyString(), anyString(), nullable(Set.class), nullable(Set.class), eq(false)))
293-
.thenThrow(new MessagingException("Test"));
297+
.thenThrow(new MessagingException("Test"));
294298

295299
controller.searchRemoteMessagesSynchronous(accountUuid, FOLDER_ID, "query", reqFlags, forbiddenFlags, listener);
296300

@@ -301,7 +305,7 @@ public void searchRemoteMessagesSynchronous_shouldNotifyOnFailure() throws Excep
301305
public void searchRemoteMessagesSynchronous_shouldNotifyOnFinish() throws Exception {
302306
setupRemoteSearch();
303307
when(backend.search(anyString(), nullable(String.class), nullable(Set.class), nullable(Set.class), eq(false)))
304-
.thenThrow(new MessagingException("Test"));
308+
.thenThrow(new MessagingException("Test"));
305309

306310
controller.searchRemoteMessagesSynchronous(accountUuid, FOLDER_ID, "query", reqFlags, forbiddenFlags, listener);
307311

@@ -423,9 +427,9 @@ private void configureAccount() {
423427
accountUuid = account.getUuid();
424428

425429
account.setIncomingServerSettings(new ServerSettings(Protocols.IMAP, "host", 993,
426-
ConnectionSecurity.SSL_TLS_REQUIRED, AuthType.PLAIN, "username", "password", null));
430+
ConnectionSecurity.SSL_TLS_REQUIRED, AuthType.PLAIN, "username", "password", null));
427431
account.setOutgoingServerSettings(new ServerSettings(Protocols.SMTP, "host", 465,
428-
ConnectionSecurity.SSL_TLS_REQUIRED, AuthType.PLAIN, "username", "password", null));
432+
ConnectionSecurity.SSL_TLS_REQUIRED, AuthType.PLAIN, "username", "password", null));
429433
account.setMaximumAutoDownloadMessageSize(MAXIMUM_SMALL_MESSAGE_SIZE);
430434
account.setEmail("[email protected]");
431435
}

legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ class MessageListFragment :
510510
onSnackbarNotificationEvent = ::onSnackbarInAppNotificationEvent,
511511
eventFilter = { event ->
512512
val accountUuid = event.notification.accountUuid
513-
accountUuid != null && accounts.any { it.uuid == accountUuid }
513+
accountUuid != null && accountUuid in accountUuids
514514
},
515515
modifier = Modifier
516516
.animateContentSize()

0 commit comments

Comments
 (0)