From 60f20fe566414443a2fb4c1e5b3db455a2896706 Mon Sep 17 00:00:00 2001 From: Rafael Tonholo Date: Fri, 26 Sep 2025 15:02:48 -0300 Subject: [PATCH 1/8] refactor(notifications): simplify notification action label resolution --- .../ResolvedNotificationActionButton.kt | 35 +++++++--------- .../ui/BannerGlobalNotificationHostTest.kt | 4 +- .../BannerInlineNotificationListHostTest.kt | 12 +++--- .../api/content/PushServiceNotification.kt | 2 +- .../api/ui/action/NotificationAction.kt | 42 +++++++++---------- .../InAppNotificationHostStateHolderTest.kt | 30 ++++++------- ...aultNotificationActionIntentCreatorTest.kt | 6 +-- .../fake/ui/action/FakeNotificationAction.kt | 4 +- 8 files changed, 63 insertions(+), 72 deletions(-) diff --git a/feature/notification/api/src/androidMain/kotlin/net/thunderbird/feature/notification/api/ui/action/ResolvedNotificationActionButton.kt b/feature/notification/api/src/androidMain/kotlin/net/thunderbird/feature/notification/api/ui/action/ResolvedNotificationActionButton.kt index a1ed25234f1..eb984a021d8 100644 --- a/feature/notification/api/src/androidMain/kotlin/net/thunderbird/feature/notification/api/ui/action/ResolvedNotificationActionButton.kt +++ b/feature/notification/api/src/androidMain/kotlin/net/thunderbird/feature/notification/api/ui/action/ResolvedNotificationActionButton.kt @@ -1,14 +1,9 @@ package net.thunderbird.feature.notification.api.ui.action import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import app.k9mail.core.ui.compose.designsystem.molecule.notification.NotificationActionButton -import kotlin.let +import org.jetbrains.compose.resources.stringResource /** * Displays a [NotificationActionButton] with the title resolved from the [NotificationAction]. @@ -27,19 +22,19 @@ internal fun ResolvedNotificationActionButton( onActionClick: (NotificationAction) -> Unit, modifier: Modifier = Modifier, ) { - var text by remember(action) { mutableStateOf(value = null) } + NotificationActionButton( + text = when (val labelResource = action.labelResource) { + null if action.label.isNotEmpty() -> action.label + null -> error( + "You must specify at least one of labelResource or label in ${ + action::class.simpleName + }", + ) - LaunchedEffect(action) { - text = action.resolveTitle() - } - - text?.let { text -> - NotificationActionButton( - text = text, - onClick = { - onActionClick(action) - }, - modifier = modifier, - ) - } + else -> stringResource(labelResource) + }, + onClick = { onActionClick(action) }, + isExternalLink = action is NotificationAction.ViewSupportArticle, + modifier = modifier, + ) } diff --git a/feature/notification/api/src/androidUnitTest/kotlin/net/thunderbird/feature/notification/api/ui/BannerGlobalNotificationHostTest.kt b/feature/notification/api/src/androidUnitTest/kotlin/net/thunderbird/feature/notification/api/ui/BannerGlobalNotificationHostTest.kt index d15a32b0a27..7441010a556 100644 --- a/feature/notification/api/src/androidUnitTest/kotlin/net/thunderbird/feature/notification/api/ui/BannerGlobalNotificationHostTest.kt +++ b/feature/notification/api/src/androidUnitTest/kotlin/net/thunderbird/feature/notification/api/ui/BannerGlobalNotificationHostTest.kt @@ -238,7 +238,7 @@ class BannerGlobalNotificationHostTest : ComposeTest() { title = title, contentText = contentText, severity = NotificationSeverity.Fatal, - action = createFakeNotificationAction(title = actionText), + action = createFakeNotificationAction(label = actionText), ) mainClock.autoAdvance = false @@ -319,7 +319,7 @@ class BannerGlobalNotificationHostTest : ComposeTest() { title = title, contentText = contentText, severity = NotificationSeverity.Fatal, - action = createFakeNotificationAction(title = actionText), + action = createFakeNotificationAction(label = actionText), ) mainClock.autoAdvance = false diff --git a/feature/notification/api/src/androidUnitTest/kotlin/net/thunderbird/feature/notification/api/ui/BannerInlineNotificationListHostTest.kt b/feature/notification/api/src/androidUnitTest/kotlin/net/thunderbird/feature/notification/api/ui/BannerInlineNotificationListHostTest.kt index 4785561b4ff..6f5ee0b9a9b 100644 --- a/feature/notification/api/src/androidUnitTest/kotlin/net/thunderbird/feature/notification/api/ui/BannerInlineNotificationListHostTest.kt +++ b/feature/notification/api/src/androidUnitTest/kotlin/net/thunderbird/feature/notification/api/ui/BannerInlineNotificationListHostTest.kt @@ -48,7 +48,7 @@ class BannerInlineNotificationListHostTest : ComposeTest() { // Arrange val title = "Notification in test" val supportingText = "The supporting text" - val action = createFakeNotificationAction(title = "Action 1") + val action = createFakeNotificationAction(label = "Action 1") val notification = createNotification(title = title, supportingText = supportingText, actions = setOf(action)) mainClock.autoAdvance = false setContentWithTheme { @@ -94,7 +94,7 @@ class BannerInlineNotificationListHostTest : ComposeTest() { actionButton .onChildren() - .filterToOne(hasTextExactly(action.title)) + .filterToOne(hasTextExactly(action.label)) .assertIsDisplayed() }, ) @@ -200,7 +200,7 @@ class BannerInlineNotificationListHostTest : ComposeTest() { // Arrange val title = "Notification in test" val supportingText = "The supporting text" - val action = createFakeNotificationAction(title = "Action 1") + val action = createFakeNotificationAction(label = "Action 1") val notification = createNotification(title = title, supportingText = supportingText, actions = setOf(action)) mainClock.autoAdvance = false val actionClicked = mutableStateOf(value = null) @@ -215,7 +215,7 @@ class BannerInlineNotificationListHostTest : ComposeTest() { onNodeWithTag(BUTTON_NOTIFICATION_TEST_TAG).performClick() // Advance Animation mainClock.advanceTimeBy(1000L) - onNodeWithText(action.title).performClick() + onNodeWithText(action.label).performClick() // Assert printSemanticTree() @@ -320,8 +320,8 @@ class BannerInlineNotificationListHostTest : ComposeTest() { title: String, supportingText: String, actions: Set = setOf( - createFakeNotificationAction(title = "Action 1"), - createFakeNotificationAction(title = "Action 2"), + createFakeNotificationAction(label = "Action 1"), + createFakeNotificationAction(label = "Action 2"), ), ): FakeInAppOnlyNotification { return FakeInAppOnlyNotification( diff --git a/feature/notification/api/src/commonMain/kotlin/net/thunderbird/feature/notification/api/content/PushServiceNotification.kt b/feature/notification/api/src/commonMain/kotlin/net/thunderbird/feature/notification/api/content/PushServiceNotification.kt index 170c01b0e1c..18c57a6f50c 100644 --- a/feature/notification/api/src/commonMain/kotlin/net/thunderbird/feature/notification/api/content/PushServiceNotification.kt +++ b/feature/notification/api/src/commonMain/kotlin/net/thunderbird/feature/notification/api/content/PushServiceNotification.kt @@ -181,7 +181,7 @@ sealed class PushServiceNotification : AppNotification(), SystemNotification { private suspend fun buildNotificationActions(): Set = setOf( NotificationAction.Tap(), NotificationAction.CustomAction( - title = getString(resource = Res.string.push_info_disable_push_action), + label = getString(resource = Res.string.push_info_disable_push_action), icon = NotificationActionIcons.DisablePushAction, ), ) diff --git a/feature/notification/api/src/commonMain/kotlin/net/thunderbird/feature/notification/api/ui/action/NotificationAction.kt b/feature/notification/api/src/commonMain/kotlin/net/thunderbird/feature/notification/api/ui/action/NotificationAction.kt index cc6cde1f070..c36e8db27cd 100644 --- a/feature/notification/api/src/commonMain/kotlin/net/thunderbird/feature/notification/api/ui/action/NotificationAction.kt +++ b/feature/notification/api/src/commonMain/kotlin/net/thunderbird/feature/notification/api/ui/action/NotificationAction.kt @@ -12,6 +12,7 @@ import net.thunderbird.feature.notification.api.ui.action.icon.UpdateServerSetti import net.thunderbird.feature.notification.api.ui.icon.NotificationIcon import net.thunderbird.feature.notification.resources.api.Res import net.thunderbird.feature.notification.resources.api.banner_inline_notification_open_notifications +import net.thunderbird.feature.notification.resources.api.banner_inline_notification_view_support_article import net.thunderbird.feature.notification.resources.api.notification_action_archive import net.thunderbird.feature.notification.resources.api.notification_action_delete import net.thunderbird.feature.notification.resources.api.notification_action_mark_as_read @@ -27,9 +28,11 @@ import org.jetbrains.compose.resources.getString */ sealed class NotificationAction { abstract val icon: NotificationIcon? - protected abstract val titleResource: StringResource? + abstract val labelResource: StringResource? + open val label: String get() = "" - open suspend fun resolveTitle(): String? = titleResource?.let { getString(it) } + open suspend fun resolveTitle(): String? = labelResource?.let { getString(it) } + ?: label.takeIf { it.isNotEmpty() } /** * Action to open the notification. This is the default action when a notification is tapped. @@ -44,7 +47,7 @@ sealed class NotificationAction { */ data class Tap(val override: NotificationAction? = null) : NotificationAction() { override val icon: NotificationIcon? = override?.icon - override val titleResource: StringResource? = override?.titleResource + override val labelResource: StringResource? = override?.labelResource } /** @@ -52,8 +55,7 @@ sealed class NotificationAction { */ data object Reply : NotificationAction() { override val icon: NotificationIcon = NotificationActionIcons.Reply - - override val titleResource: StringResource = Res.string.notification_action_reply + override val labelResource: StringResource = Res.string.notification_action_reply } /** @@ -61,8 +63,7 @@ sealed class NotificationAction { */ data object MarkAsRead : NotificationAction() { override val icon: NotificationIcon = NotificationActionIcons.MarkAsRead - - override val titleResource: StringResource = Res.string.notification_action_mark_as_read + override val labelResource: StringResource = Res.string.notification_action_mark_as_read } /** @@ -70,8 +71,7 @@ sealed class NotificationAction { */ data object Delete : NotificationAction() { override val icon: NotificationIcon = NotificationActionIcons.Delete - - override val titleResource: StringResource = Res.string.notification_action_delete + override val labelResource: StringResource = Res.string.notification_action_delete } /** @@ -79,8 +79,7 @@ sealed class NotificationAction { */ data object MarkAsSpam : NotificationAction() { override val icon: NotificationIcon = NotificationActionIcons.MarkAsSpam - - override val titleResource: StringResource = Res.string.notification_action_spam + override val labelResource: StringResource = Res.string.notification_action_spam } /** @@ -88,8 +87,7 @@ sealed class NotificationAction { */ data object Archive : NotificationAction() { override val icon: NotificationIcon = NotificationActionIcons.Archive - - override val titleResource: StringResource = Res.string.notification_action_archive + override val labelResource: StringResource = Res.string.notification_action_archive } /** @@ -97,7 +95,7 @@ sealed class NotificationAction { */ data class UpdateIncomingServerSettings(val accountUuid: String, val accountNumber: Int) : NotificationAction() { override val icon: NotificationIcon = NotificationActionIcons.UpdateServerSettings - override val titleResource: StringResource = Res.string.notification_action_update_server_settings + override val labelResource: StringResource = Res.string.notification_action_update_server_settings } /** @@ -105,7 +103,7 @@ sealed class NotificationAction { */ data class UpdateOutgoingServerSettings(val accountUuid: String, val accountNumber: Int) : NotificationAction() { override val icon: NotificationIcon = NotificationActionIcons.UpdateServerSettings - override val titleResource: StringResource = Res.string.notification_action_update_server_settings + override val labelResource: StringResource = Res.string.notification_action_update_server_settings } /** @@ -113,8 +111,7 @@ sealed class NotificationAction { */ data object Retry : NotificationAction() { override val icon: NotificationIcon = NotificationActionIcons.Retry - - override val titleResource: StringResource = Res.string.notification_action_retry + override val labelResource: StringResource = Res.string.notification_action_retry } /** @@ -122,7 +119,7 @@ sealed class NotificationAction { */ data object OpenNotificationCentre : NotificationAction() { override val icon: NotificationIcon? = null - override val titleResource: StringResource = Res.string.banner_inline_notification_open_notifications + override val labelResource: StringResource = Res.string.banner_inline_notification_open_notifications } /** @@ -130,14 +127,13 @@ sealed class NotificationAction { * * This can be used for actions that are not predefined and require a specific message. * - * @property title The text to be displayed for this custom action. + * @property label The text to be displayed for this custom action. */ data class CustomAction( - val title: String, + override val label: String, override val icon: NotificationIcon? = null, ) : NotificationAction() { - override val titleResource: StringResource get() = error("Custom Action must not supply a title resource") - - override suspend fun resolveTitle(): String = title + override val labelResource: StringResource? get() = null + override suspend fun resolveTitle(): String = label } } diff --git a/feature/notification/api/src/commonTest/kotlin/net/thunderbird/feature/notification/api/ui/host/InAppNotificationHostStateHolderTest.kt b/feature/notification/api/src/commonTest/kotlin/net/thunderbird/feature/notification/api/ui/host/InAppNotificationHostStateHolderTest.kt index e9d49a19d13..035b7b74912 100644 --- a/feature/notification/api/src/commonTest/kotlin/net/thunderbird/feature/notification/api/ui/host/InAppNotificationHostStateHolderTest.kt +++ b/feature/notification/api/src/commonTest/kotlin/net/thunderbird/feature/notification/api/ui/host/InAppNotificationHostStateHolderTest.kt @@ -138,8 +138,8 @@ class InAppNotificationHostStateHolderTest { val notification = FakeInAppOnlyNotification( inAppNotificationStyle = inAppNotificationStyle { bannerGlobal() }, actions = setOf( - createFakeNotificationAction(title = "fake action 1"), - createFakeNotificationAction(title = "fake action 2"), + createFakeNotificationAction(label = "fake action 1"), + createFakeNotificationAction(label = "fake action 2"), ), ) val flags = persistentSetOf() @@ -344,10 +344,10 @@ class InAppNotificationHostStateHolderTest { } fun getAction(index: Int): NotificationAction = when (index) { - in 0..<25 -> createFakeNotificationAction(title = "fake action 1") - in 25..<50 -> createFakeNotificationAction(title = "fake action 2") - in 50..<75 -> createFakeNotificationAction(title = "fake action 3") - else -> createFakeNotificationAction(title = "fake action 4") + in 0..<25 -> createFakeNotificationAction(label = "fake action 1") + in 25..<50 -> createFakeNotificationAction(label = "fake action 2") + in 50..<75 -> createFakeNotificationAction(label = "fake action 3") + else -> createFakeNotificationAction(label = "fake action 4") } val expectedSize = 100 @@ -555,9 +555,9 @@ class InAppNotificationHostStateHolderTest { inAppNotificationStyle = inAppNotificationStyle { bannerInline() }, contentText = "not important in this test case", actions = setOf( - createFakeNotificationAction(title = "fake action 1"), - createFakeNotificationAction(title = "fake action 2"), - createFakeNotificationAction(title = "fake action 3"), + createFakeNotificationAction(label = "fake action 1"), + createFakeNotificationAction(label = "fake action 2"), + createFakeNotificationAction(label = "fake action 3"), ), ) val flags = persistentSetOf() @@ -703,8 +703,8 @@ class InAppNotificationHostStateHolderTest { val notification = FakeInAppOnlyNotification( inAppNotificationStyle = inAppNotificationStyle { snackbar() }, actions = setOf( - createFakeNotificationAction(title = "fake action 1"), - createFakeNotificationAction(title = "fake action 2"), + createFakeNotificationAction(label = "fake action 1"), + createFakeNotificationAction(label = "fake action 2"), ), ) val flags = persistentSetOf() @@ -810,10 +810,10 @@ class InAppNotificationHostStateHolderTest { } fun getAction(index: Int): NotificationAction = when (index) { - in 0..<25 -> createFakeNotificationAction(title = "fake action 1") - in 25..<50 -> createFakeNotificationAction(title = "fake action 2") - in 50..<75 -> createFakeNotificationAction(title = "fake action 3") - else -> createFakeNotificationAction(title = "fake action 4") + in 0..<25 -> createFakeNotificationAction(label = "fake action 1") + in 25..<50 -> createFakeNotificationAction(label = "fake action 2") + in 50..<75 -> createFakeNotificationAction(label = "fake action 3") + else -> createFakeNotificationAction(label = "fake action 4") } val expectedSize = 99 diff --git a/feature/notification/impl/src/androidUnitTest/kotlin/net/thunderbird/feature/notification/impl/intent/action/DefaultNotificationActionIntentCreatorTest.kt b/feature/notification/impl/src/androidUnitTest/kotlin/net/thunderbird/feature/notification/impl/intent/action/DefaultNotificationActionIntentCreatorTest.kt index 71dca5a0c33..8bab43e2925 100644 --- a/feature/notification/impl/src/androidUnitTest/kotlin/net/thunderbird/feature/notification/impl/intent/action/DefaultNotificationActionIntentCreatorTest.kt +++ b/feature/notification/impl/src/androidUnitTest/kotlin/net/thunderbird/feature/notification/impl/intent/action/DefaultNotificationActionIntentCreatorTest.kt @@ -43,9 +43,9 @@ class DefaultNotificationActionIntentCreatorTest { NotificationAction.UpdateIncomingServerSettings("uuid", 1), NotificationAction.UpdateOutgoingServerSettings("uuid", 1), NotificationAction.Retry, - NotificationAction.CustomAction(title = "Custom Action 1"), - NotificationAction.CustomAction(title = "Custom Action 2"), - NotificationAction.CustomAction(title = "Custom Action 3"), + NotificationAction.CustomAction(label = "Custom Action 1"), + NotificationAction.CustomAction(label = "Custom Action 2"), + NotificationAction.CustomAction(label = "Custom Action 3"), ) val testSubject = createTestSubject() diff --git a/feature/notification/testing/src/commonMain/kotlin/net/thunderbird/feature/notification/testing/fake/ui/action/FakeNotificationAction.kt b/feature/notification/testing/src/commonMain/kotlin/net/thunderbird/feature/notification/testing/fake/ui/action/FakeNotificationAction.kt index f2aa04c637f..1f206ef6da1 100644 --- a/feature/notification/testing/src/commonMain/kotlin/net/thunderbird/feature/notification/testing/fake/ui/action/FakeNotificationAction.kt +++ b/feature/notification/testing/src/commonMain/kotlin/net/thunderbird/feature/notification/testing/fake/ui/action/FakeNotificationAction.kt @@ -4,9 +4,9 @@ import net.thunderbird.feature.notification.api.ui.action.NotificationAction import net.thunderbird.feature.notification.api.ui.icon.NotificationIcon import net.thunderbird.feature.notification.testing.fake.icon.EMPTY_SYSTEM_NOTIFICATION_ICON -fun createFakeNotificationAction(title: String = "fake action"): NotificationAction.CustomAction = +fun createFakeNotificationAction(label: String = "fake action"): NotificationAction.CustomAction = NotificationAction.CustomAction( - title = title, + label = label, icon = NotificationIcon( systemNotificationIcon = EMPTY_SYSTEM_NOTIFICATION_ICON, ), From 79ae7cf35f75c91f3b92fe4a114ad7cf247b70bb Mon Sep 17 00:00:00 2001 From: Rafael Tonholo Date: Fri, 26 Sep 2025 15:04:28 -0300 Subject: [PATCH 2/8] feat(notifications): add view support article notification action --- .../commonMain/composeResources/values/strings.xml | 1 + .../api/ui/action/NotificationAction.kt | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/feature/notification/api/src/commonMain/composeResources/values/strings.xml b/feature/notification/api/src/commonMain/composeResources/values/strings.xml index 0be6954a6de..d07dac24ffb 100644 --- a/feature/notification/api/src/commonMain/composeResources/values/strings.xml +++ b/feature/notification/api/src/commonMain/composeResources/values/strings.xml @@ -60,4 +60,5 @@ Check Error Notifications Some messages need your attention. Open notifications + View support article diff --git a/feature/notification/api/src/commonMain/kotlin/net/thunderbird/feature/notification/api/ui/action/NotificationAction.kt b/feature/notification/api/src/commonMain/kotlin/net/thunderbird/feature/notification/api/ui/action/NotificationAction.kt index c36e8db27cd..be4a3dcf3d1 100644 --- a/feature/notification/api/src/commonMain/kotlin/net/thunderbird/feature/notification/api/ui/action/NotificationAction.kt +++ b/feature/notification/api/src/commonMain/kotlin/net/thunderbird/feature/notification/api/ui/action/NotificationAction.kt @@ -136,4 +136,17 @@ sealed class NotificationAction { override val labelResource: StringResource? get() = null override suspend fun resolveTitle(): String = label } + + /** + * Action to open a support article in a web browser. + * + * This action is typically used to provide users with more information or help related to + * the notification's content. + * + * @property url The URL of the support article to be opened. + */ + data class ViewSupportArticle(val url: String) : NotificationAction() { + override val icon: NotificationIcon? = null + override val labelResource: StringResource = Res.string.banner_inline_notification_view_support_article + } } From 2529017319e8333f111bc0cb88dd8fc905b53144 Mon Sep 17 00:00:00 2001 From: Rafael Tonholo Date: Fri, 26 Sep 2025 15:05:36 -0300 Subject: [PATCH 3/8] feat(notifications): add error notifications dialog composable --- .../api/ui/dialog/ErrorNotificationsDialog.kt | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 feature/notification/api/src/androidMain/kotlin/net/thunderbird/feature/notification/api/ui/dialog/ErrorNotificationsDialog.kt diff --git a/feature/notification/api/src/androidMain/kotlin/net/thunderbird/feature/notification/api/ui/dialog/ErrorNotificationsDialog.kt b/feature/notification/api/src/androidMain/kotlin/net/thunderbird/feature/notification/api/ui/dialog/ErrorNotificationsDialog.kt new file mode 100644 index 00000000000..f893c25d594 --- /dev/null +++ b/feature/notification/api/src/androidMain/kotlin/net/thunderbird/feature/notification/api/ui/dialog/ErrorNotificationsDialog.kt @@ -0,0 +1,113 @@ +package net.thunderbird.feature.notification.api.ui.dialog + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import app.k9mail.core.ui.compose.designsystem.atom.Surface +import app.k9mail.core.ui.compose.designsystem.atom.button.ButtonIcon +import app.k9mail.core.ui.compose.designsystem.atom.icon.Icons +import app.k9mail.core.ui.compose.designsystem.organism.TopAppBar +import app.k9mail.core.ui.compose.designsystem.organism.banner.inline.BannerInlineNotificationCardBehaviour +import app.k9mail.core.ui.compose.designsystem.organism.banner.inline.ErrorBannerInlineNotificationCard +import app.k9mail.core.ui.compose.theme2.MainTheme +import kotlinx.collections.immutable.ImmutableSet +import net.thunderbird.core.ui.compose.common.modifier.testTagAsResourceId +import net.thunderbird.feature.notification.api.ui.action.NotificationAction +import net.thunderbird.feature.notification.api.ui.action.ResolvedNotificationActionButton +import net.thunderbird.feature.notification.api.ui.host.visual.BannerInlineVisual + +@Composable +fun ErrorNotificationsDialog( + visuals: ImmutableSet, + onDismiss: () -> Unit, + onNotificationActionClick: (NotificationAction) -> Unit, + modifier: Modifier = Modifier, + properties: DialogProperties = ErrorNotificationsDialogDefaults.defaultProperties, +) { + val showDialog = remember(visuals) { visuals.isNotEmpty() } + if (showDialog) { + Dialog( + onDismissRequest = onDismiss, + properties = properties, + ) { + ErrorNotificationsDialogContent( + bannerInlineVisuals = visuals, + onDismiss = onDismiss, + onNotificationActionClick = onNotificationActionClick, + modifier = modifier, + ) + } + } +} + +@Composable +private fun ErrorNotificationsDialogContent( + bannerInlineVisuals: ImmutableSet, + onDismiss: () -> Unit, + onNotificationActionClick: (NotificationAction) -> Unit, + modifier: Modifier = Modifier, +) { + Surface(modifier = modifier) { + Column { + TopAppBar( + title = "Error Notifications", + navigationIcon = { + ButtonIcon(onClick = onDismiss, imageVector = Icons.Outlined.Close) + }, + ) + LazyColumn( + verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.half), + contentPadding = PaddingValues( + top = MainTheme.spacings.half, + start = MainTheme.spacings.double, + end = MainTheme.spacings.double, + bottom = MainTheme.spacings.double, + ), + ) { + items( + count = bannerInlineVisuals.size, + ) { index -> + val item = bannerInlineVisuals.elementAt(index) + ErrorBannerInlineNotificationCard( + title = item.title, + supportingText = item.supportingText, + actions = { + item.actions.forEachIndexed { actionIndex, action -> + ResolvedNotificationActionButton( + action = action, + onActionClick = onNotificationActionClick, + modifier = Modifier.testTagAsResourceId( + tag = ErrorNotificationsDialogDefaults.testTagBannerInlineListItemAction( + index = index, + actionIndex = actionIndex, + ), + ), + ) + } + }, + behaviour = BannerInlineNotificationCardBehaviour.Expanded, + ) + } + } + } + } +} + +object ErrorNotificationsDialogDefaults { + const val DEFAULT_TAG = "error_notifications_dialog" + + val defaultProperties = DialogProperties( + dismissOnBackPress = true, + dismissOnClickOutside = false, + usePlatformDefaultWidth = false, + ) + + internal fun testTagBannerInlineListItemAction(index: Int, actionIndex: Int) = + "${DEFAULT_TAG}_banner_inline_notification_list_item_action_${index}_$actionIndex" +} From 4a68be2f7b14ce5aad5979750bd605c7c2411a1f Mon Sep 17 00:00:00 2001 From: Rafael Tonholo Date: Tue, 30 Sep 2025 09:36:54 -0300 Subject: [PATCH 4/8] fix(notifications): MessageListAdapter in-app notification filter not considering all accounts --- .../k9/ui/messagelist/MessageListAdapter.kt | 24 +++---------------- .../k9/ui/messagelist/MessageListFragment.kt | 10 ++++---- 2 files changed, 9 insertions(+), 25 deletions(-) diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListAdapter.kt b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListAdapter.kt index 054ad19ed85..2b780009eb3 100644 --- a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListAdapter.kt +++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListAdapter.kt @@ -29,6 +29,7 @@ import net.thunderbird.core.featureflag.FeatureFlagProvider import net.thunderbird.core.featureflag.FeatureFlagResult import net.thunderbird.core.ui.theme.api.FeatureThemeProvider import net.thunderbird.feature.account.avatar.AvatarMonogramCreator +import net.thunderbird.feature.notification.api.receiver.InAppNotificationEvent import net.thunderbird.feature.notification.api.ui.action.NotificationAction private const val FOOTER_ID = 1L @@ -223,27 +224,7 @@ class MessageListAdapter internal constructor( TYPE_IN_APP_NOTIFICATION_BANNER_INLINE_LIST if isInAppNotificationEnabled -> BannerInlineListInAppNotificationViewHolder( view = ComposeView(context = parent.context), - eventFilter = { event -> - val accountUuid = event.notification.accountUuid - accountUuid != null && accountUuid in accountUuids - }, - onNotificationActionClick = { action -> - when (action) { - is NotificationAction.UpdateIncomingServerSettings -> - FeatureLauncherActivity.launch( - context = parent.context, - target = FeatureLauncherTarget.AccountEditIncomingSettings(action.accountUuid), - ) - - is NotificationAction.UpdateOutgoingServerSettings -> - FeatureLauncherActivity.launch( - context = parent.context, - target = FeatureLauncherTarget.AccountEditOutgoingSettings(action.accountUuid), - ) - - else -> Unit - } - }, + eventFilter = listItemListener::filterInAppNotificationEvents, ) else -> error("Unsupported type: $viewType") @@ -429,6 +410,7 @@ interface MessageListItemActionListener { fun onToggleMessageSelection(item: MessageListItem) fun onToggleMessageFlag(item: MessageListItem) fun onFooterClicked() + fun filterInAppNotificationEvents(event: InAppNotificationEvent): Boolean } sealed interface MessageListViewItem { diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListFragment.kt b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListFragment.kt index c9029a790d1..3a589479227 100644 --- a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListFragment.kt +++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListFragment.kt @@ -520,10 +520,7 @@ class MessageListFragment : DisplayInAppNotificationFlag.SnackbarNotifications, ), onSnackbarNotificationEvent = ::onSnackbarInAppNotificationEvent, - eventFilter = { event -> - val accountUuid = event.notification.accountUuid - accountUuid != null && accountUuid in accountUuids - }, + eventFilter = ::filterInAppNotificationEvents, modifier = Modifier .animateContentSize() .onSizeChanged { size -> @@ -2165,6 +2162,11 @@ class MessageListFragment : } } + override fun filterInAppNotificationEvents(event: InAppNotificationEvent): Boolean { + val accountUuid = event.notification.accountUuid + return accountUuid != null && accountUuid in accountUuids + } + internal inner class MessageListActivityListener : SimpleMessagingListener() { private val lock = Any() From 7a81dfe0ffb2df76f4cda0fb61b6a55d07379c72 Mon Sep 17 00:00:00 2001 From: Rafael Tonholo Date: Tue, 30 Sep 2025 09:40:03 -0300 Subject: [PATCH 5/8] chore(notifications): add dialog fragment enabling showing error notification dialog on xml --- ...ErrorNotificationsDialogFragmentFactory.kt | 12 +++ .../ui/host/visual/InAppNotificationVisual.kt | 2 +- feature/notification/impl/build.gradle.kts | 1 + .../impl/inject/NotificationModule.android.kt | 4 + .../ErrorNotificationsDialogFragment.kt | 87 +++++++++++++++++++ .../k9/ui/messagelist/MessageListAdapter.kt | 4 +- .../k9/ui/messagelist/MessageListFragment.kt | 33 ++++++- 7 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 feature/notification/api/src/androidMain/kotlin/net/thunderbird/feature/notification/api/ui/dialog/ErrorNotificationsDialogFragmentFactory.kt create mode 100644 feature/notification/impl/src/androidMain/kotlin/net/thunderbird/feature/notification/impl/ui/dialog/ErrorNotificationsDialogFragment.kt diff --git a/feature/notification/api/src/androidMain/kotlin/net/thunderbird/feature/notification/api/ui/dialog/ErrorNotificationsDialogFragmentFactory.kt b/feature/notification/api/src/androidMain/kotlin/net/thunderbird/feature/notification/api/ui/dialog/ErrorNotificationsDialogFragmentFactory.kt new file mode 100644 index 00000000000..2d142133288 --- /dev/null +++ b/feature/notification/api/src/androidMain/kotlin/net/thunderbird/feature/notification/api/ui/dialog/ErrorNotificationsDialogFragmentFactory.kt @@ -0,0 +1,12 @@ +package net.thunderbird.feature.notification.api.ui.dialog + +import androidx.fragment.app.FragmentManager +import net.thunderbird.feature.notification.api.ui.action.NotificationAction + +fun interface ErrorNotificationsDialogFragmentFactory { + fun show(fragmentManager: FragmentManager) +} + +fun interface ErrorNotificationsDialogFragmentActionListener { + fun onNotificationActionClick(action: NotificationAction) +} diff --git a/feature/notification/api/src/commonMain/kotlin/net/thunderbird/feature/notification/api/ui/host/visual/InAppNotificationVisual.kt b/feature/notification/api/src/commonMain/kotlin/net/thunderbird/feature/notification/api/ui/host/visual/InAppNotificationVisual.kt index 5ad9a41d1ef..0d84e0338bf 100644 --- a/feature/notification/api/src/commonMain/kotlin/net/thunderbird/feature/notification/api/ui/host/visual/InAppNotificationVisual.kt +++ b/feature/notification/api/src/commonMain/kotlin/net/thunderbird/feature/notification/api/ui/host/visual/InAppNotificationVisual.kt @@ -95,7 +95,7 @@ data class BannerInlineVisual( val severity: NotificationSeverity, val actions: ImmutableList, ) : InAppNotificationVisual { - internal companion object { + companion object { internal const val MAX_TITLE_LENGTH = 100 internal const val MAX_SUPPORTING_TEXT_LENGTH = 200 diff --git a/feature/notification/impl/build.gradle.kts b/feature/notification/impl/build.gradle.kts index b28b48d4dc5..b1b177929f4 100644 --- a/feature/notification/impl/build.gradle.kts +++ b/feature/notification/impl/build.gradle.kts @@ -19,6 +19,7 @@ kotlin { androidMain.dependencies { // should split feature.launcher into api/impl? implementation(projects.feature.launcher) + implementation(projects.core.ui.theme.api) } androidUnitTest.dependencies { implementation(libs.androidx.test.core) diff --git a/feature/notification/impl/src/androidMain/kotlin/net/thunderbird/feature/notification/impl/inject/NotificationModule.android.kt b/feature/notification/impl/src/androidMain/kotlin/net/thunderbird/feature/notification/impl/inject/NotificationModule.android.kt index 25fac51a3c9..3d22a750f0a 100644 --- a/feature/notification/impl/src/androidMain/kotlin/net/thunderbird/feature/notification/impl/inject/NotificationModule.android.kt +++ b/feature/notification/impl/src/androidMain/kotlin/net/thunderbird/feature/notification/impl/inject/NotificationModule.android.kt @@ -2,6 +2,7 @@ package net.thunderbird.feature.notification.impl.inject import net.thunderbird.feature.notification.api.content.SystemNotification import net.thunderbird.feature.notification.api.receiver.NotificationNotifier +import net.thunderbird.feature.notification.api.ui.dialog.ErrorNotificationsDialogFragmentFactory import net.thunderbird.feature.notification.impl.intent.action.AlarmPermissionMissingNotificationTapActionIntentCreator import net.thunderbird.feature.notification.impl.intent.action.DefaultNotificationActionIntentCreator import net.thunderbird.feature.notification.impl.intent.action.NotificationActionIntentCreator @@ -10,6 +11,7 @@ import net.thunderbird.feature.notification.impl.receiver.AndroidSystemNotificat import net.thunderbird.feature.notification.impl.receiver.SystemNotificationNotifier import net.thunderbird.feature.notification.impl.ui.action.DefaultSystemNotificationActionCreator import net.thunderbird.feature.notification.impl.ui.action.NotificationActionCreator +import net.thunderbird.feature.notification.impl.ui.dialog.ErrorNotificationsDialogFragment import org.koin.android.ext.koin.androidApplication import org.koin.core.module.Module import org.koin.core.qualifier.named @@ -51,4 +53,6 @@ internal actual val platformFeatureNotificationModule: Module = module { }.onClose { notifier -> notifier?.dispose() } + + factory { ErrorNotificationsDialogFragment.Factory } } diff --git a/feature/notification/impl/src/androidMain/kotlin/net/thunderbird/feature/notification/impl/ui/dialog/ErrorNotificationsDialogFragment.kt b/feature/notification/impl/src/androidMain/kotlin/net/thunderbird/feature/notification/impl/ui/dialog/ErrorNotificationsDialogFragment.kt new file mode 100644 index 00000000000..a584e8d1b30 --- /dev/null +++ b/feature/notification/impl/src/androidMain/kotlin/net/thunderbird/feature/notification/impl/ui/dialog/ErrorNotificationsDialogFragment.kt @@ -0,0 +1,87 @@ +package net.thunderbird.feature.notification.impl.ui.dialog + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.Window +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ComposeView +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.FragmentManager +import kotlinx.collections.immutable.ImmutableSet +import kotlinx.collections.immutable.toPersistentSet +import net.thunderbird.core.ui.theme.api.FeatureThemeProvider +import net.thunderbird.feature.notification.api.NotificationRegistry +import net.thunderbird.feature.notification.api.content.InAppNotification +import net.thunderbird.feature.notification.api.ui.dialog.ErrorNotificationsDialog +import net.thunderbird.feature.notification.api.ui.dialog.ErrorNotificationsDialogFragmentActionListener +import net.thunderbird.feature.notification.api.ui.dialog.ErrorNotificationsDialogFragmentFactory +import net.thunderbird.feature.notification.api.ui.host.visual.BannerInlineVisual +import net.thunderbird.feature.notification.api.ui.style.InAppNotificationStyle +import org.koin.android.ext.android.inject + +internal class ErrorNotificationsDialogFragment : DialogFragment() { + private val registry: NotificationRegistry by inject() + private var visuals = getVisuals() + private var listener: ErrorNotificationsDialogFragmentActionListener? = null + + private val themeProvider: FeatureThemeProvider by inject() + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View? { + dialog?.requestWindowFeature(Window.FEATURE_NO_TITLE) + return ComposeView(requireContext()).apply { + setContent { + themeProvider.WithTheme { + ErrorNotificationsDialog( + visuals = visuals, + onDismiss = { dismiss() }, + onNotificationActionClick = { notificationAction -> + dismiss() + listener?.onNotificationActionClick(notificationAction) + }, + modifier = Modifier.fillMaxSize(), + ) + } + } + } + } + + override fun onAttach(context: Context) { + super.onAttach(context) + listener = parentFragment as? ErrorNotificationsDialogFragmentActionListener + ?: context as? ErrorNotificationsDialogFragmentActionListener + } + + override fun onDetach() { + super.onDetach() + listener = null + } + + override fun onResume() { + super.onResume() + visuals = getVisuals() + } + + private fun getVisuals(): ImmutableSet = registry + .registrar + .values + .asSequence() + .filterIsInstance() + .filter { it.inAppNotificationStyle is InAppNotificationStyle.BannerInlineNotification } + .mapNotNull { BannerInlineVisual.from(it) } + .toPersistentSet() + + companion object Factory : ErrorNotificationsDialogFragmentFactory { + private const val TAG = "ErrorNotificationsDialogFragment" + + override fun show(fragmentManager: FragmentManager) { + ErrorNotificationsDialogFragment().show(fragmentManager, TAG) + } + } +} diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListAdapter.kt b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListAdapter.kt index 2b780009eb3..6d2dacffe2e 100644 --- a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListAdapter.kt +++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListAdapter.kt @@ -12,8 +12,6 @@ import androidx.compose.ui.platform.ComposeView import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import app.k9mail.core.android.common.contact.ContactRepository -import app.k9mail.feature.launcher.FeatureLauncherActivity -import app.k9mail.feature.launcher.FeatureLauncherTarget import app.k9mail.legacy.message.controller.MessageReference import com.fsck.k9.contacts.ContactPictureLoader import com.fsck.k9.ui.helper.RelativeDateTimeFormatter @@ -225,6 +223,7 @@ class MessageListAdapter internal constructor( BannerInlineListInAppNotificationViewHolder( view = ComposeView(context = parent.context), eventFilter = listItemListener::filterInAppNotificationEvents, + onNotificationActionClick = listItemListener::onNotificationActionClicked, ) else -> error("Unsupported type: $viewType") @@ -411,6 +410,7 @@ interface MessageListItemActionListener { fun onToggleMessageFlag(item: MessageListItem) fun onFooterClicked() fun filterInAppNotificationEvents(event: InAppNotificationEvent): Boolean + fun onNotificationActionClicked(action: NotificationAction) } sealed interface MessageListViewItem { diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListFragment.kt b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListFragment.kt index 3a589479227..b106f8124ff 100644 --- a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListFragment.kt +++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListFragment.kt @@ -35,6 +35,8 @@ import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import app.k9mail.core.android.common.contact.ContactRepository +import app.k9mail.feature.launcher.FeatureLauncherActivity +import app.k9mail.feature.launcher.FeatureLauncherTarget import app.k9mail.legacy.message.controller.MessageReference import app.k9mail.legacy.message.controller.MessagingControllerRegistry import app.k9mail.legacy.message.controller.SimpleMessagingListener @@ -101,7 +103,11 @@ import net.thunderbird.feature.account.avatar.AvatarMonogramCreator import net.thunderbird.feature.mail.folder.api.OutboxFolderManager import net.thunderbird.feature.mail.message.list.domain.DomainContract import net.thunderbird.feature.mail.message.list.ui.dialog.SetupArchiveFolderDialogFragmentFactory +import net.thunderbird.feature.notification.api.receiver.InAppNotificationEvent import net.thunderbird.feature.notification.api.ui.InAppNotificationHost +import net.thunderbird.feature.notification.api.ui.action.NotificationAction +import net.thunderbird.feature.notification.api.ui.dialog.ErrorNotificationsDialogFragmentActionListener +import net.thunderbird.feature.notification.api.ui.dialog.ErrorNotificationsDialogFragmentFactory import net.thunderbird.feature.notification.api.ui.host.DisplayInAppNotificationFlag import net.thunderbird.feature.notification.api.ui.host.visual.SnackbarVisual import net.thunderbird.feature.notification.api.ui.style.SnackbarDuration @@ -121,7 +127,8 @@ private const val TAG = "MessageListFragment" class MessageListFragment : Fragment(), ConfirmationDialogFragmentListener, - MessageListItemActionListener { + MessageListItemActionListener, + ErrorNotificationsDialogFragmentActionListener { val viewModel: MessageListViewModel by viewModel() private val recentChangesViewModel: RecentChangesViewModel by viewModel() @@ -147,6 +154,7 @@ class MessageListFragment : private val logger: Logger by inject() private val outboxFolderManager: OutboxFolderManager by inject() private val authDebugActions: AuthDebugActions by inject() + private val errorNotificationsDialogFragmentFactory: ErrorNotificationsDialogFragmentFactory by inject() private val handler = MessageListHandler(this) private val activityListener = MessageListActivityListener() @@ -2167,6 +2175,29 @@ class MessageListFragment : return accountUuid != null && accountUuid in accountUuids } + override fun onNotificationActionClicked(action: NotificationAction) = onNotificationActionClick(action) + + override fun onNotificationActionClick(action: NotificationAction) { + when (action) { + is NotificationAction.UpdateIncomingServerSettings -> + FeatureLauncherActivity.launch( + context = requireContext(), + target = FeatureLauncherTarget.AccountEditIncomingSettings(action.accountUuid), + ) + + is NotificationAction.UpdateOutgoingServerSettings -> + FeatureLauncherActivity.launch( + context = requireContext(), + target = FeatureLauncherTarget.AccountEditOutgoingSettings(action.accountUuid), + ) + + is NotificationAction.OpenNotificationCentre -> + errorNotificationsDialogFragmentFactory.show(fragmentManager = childFragmentManager) + + else -> Unit + } + } + internal inner class MessageListActivityListener : SimpleMessagingListener() { private val lock = Any() From d616f3d6a27de54b845a4106d16fbddf918deb04 Mon Sep 17 00:00:00 2001 From: Rafael Tonholo Date: Tue, 30 Sep 2025 10:20:12 -0300 Subject: [PATCH 6/8] fix(notifications): banner inline list animation flicking --- .../api/ui/BannerInlineNotificationListHost.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/feature/notification/api/src/androidMain/kotlin/net/thunderbird/feature/notification/api/ui/BannerInlineNotificationListHost.kt b/feature/notification/api/src/androidMain/kotlin/net/thunderbird/feature/notification/api/ui/BannerInlineNotificationListHost.kt index c25ad7afc2e..24c23c79cf6 100644 --- a/feature/notification/api/src/androidMain/kotlin/net/thunderbird/feature/notification/api/ui/BannerInlineNotificationListHost.kt +++ b/feature/notification/api/src/androidMain/kotlin/net/thunderbird/feature/notification/api/ui/BannerInlineNotificationListHost.kt @@ -1,6 +1,7 @@ package net.thunderbird.feature.notification.api.ui import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.animateContentSize import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth @@ -69,6 +70,7 @@ fun BannerInlineNotificationListHost( targetState = bannerInlineSet, modifier = modifier.testTagAsResourceId(TEST_TAG_HOST_PARENT), transitionSpec = { bannerSlideInSlideOutAnimationSpec() }, + contentKey = { it.isEmpty() }, ) { bannerInlineSet -> if (bannerInlineSet.isNotEmpty()) { BannerInlineNotificationListHostLayout( @@ -121,6 +123,7 @@ private fun BannerInlineNotificationListHostLayout( } }, behaviour = BannerInlineNotificationCardBehaviour.Clipped, + modifier = Modifier.animateContentSize(), ) } @@ -139,7 +142,9 @@ private fun BannerInlineNotificationListHostLayout( modifier = Modifier.testTagAsResourceId(TEST_TAG_CHECK_ERROR_NOTIFICATIONS_ACTION), ) }, - modifier = Modifier.testTagAsResourceId(TEST_TAG_CHECK_ERROR_NOTIFICATIONS), + modifier = Modifier + .testTagAsResourceId(TEST_TAG_CHECK_ERROR_NOTIFICATIONS) + .animateContentSize(), ) } } From a0c53658d96919b4e6e7ca2e7126a30e6c2eb1fa Mon Sep 17 00:00:00 2001 From: Rafael Tonholo Date: Tue, 30 Sep 2025 11:25:36 -0300 Subject: [PATCH 7/8] chore: add detekt suppress on MessageListFragment --- .../main/java/com/fsck/k9/ui/messagelist/MessageListFragment.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListFragment.kt b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListFragment.kt index b106f8124ff..a629199214e 100644 --- a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListFragment.kt +++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListFragment.kt @@ -124,6 +124,7 @@ private const val RECENT_CHANGES_SNACKBAR_DURATION = 10 * 1000 private const val TAG = "MessageListFragment" +@Suppress("LargeClass", "TooManyFunctions") class MessageListFragment : Fragment(), ConfirmationDialogFragmentListener, From 7f5b3edd61b9ad775398581fe6c5634fda3996bb Mon Sep 17 00:00:00 2001 From: Rafael Tonholo Date: Tue, 30 Sep 2025 15:37:13 -0300 Subject: [PATCH 8/8] chore(notifications): increase banner inline item spacing --- .../notification/api/ui/dialog/ErrorNotificationsDialog.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/feature/notification/api/src/androidMain/kotlin/net/thunderbird/feature/notification/api/ui/dialog/ErrorNotificationsDialog.kt b/feature/notification/api/src/androidMain/kotlin/net/thunderbird/feature/notification/api/ui/dialog/ErrorNotificationsDialog.kt index f893c25d594..f3460e17c31 100644 --- a/feature/notification/api/src/androidMain/kotlin/net/thunderbird/feature/notification/api/ui/dialog/ErrorNotificationsDialog.kt +++ b/feature/notification/api/src/androidMain/kotlin/net/thunderbird/feature/notification/api/ui/dialog/ErrorNotificationsDialog.kt @@ -62,9 +62,9 @@ private fun ErrorNotificationsDialogContent( }, ) LazyColumn( - verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.half), + verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.default), contentPadding = PaddingValues( - top = MainTheme.spacings.half, + top = MainTheme.spacings.default, start = MainTheme.spacings.double, end = MainTheme.spacings.double, bottom = MainTheme.spacings.double,