Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -121,6 +123,7 @@ private fun BannerInlineNotificationListHostLayout(
}
},
behaviour = BannerInlineNotificationCardBehaviour.Clipped,
modifier = Modifier.animateContentSize(),
)
}

Expand All @@ -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(),
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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].
Expand All @@ -27,19 +22,19 @@ internal fun ResolvedNotificationActionButton(
onActionClick: (NotificationAction) -> Unit,
modifier: Modifier = Modifier,
) {
var text by remember(action) { mutableStateOf<String?>(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,
)
}
Original file line number Diff line number Diff line change
@@ -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<BannerInlineVisual>,
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<BannerInlineVisual>,
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.default),
contentPadding = PaddingValues(
top = MainTheme.spacings.default,
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"
}
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -94,7 +94,7 @@ class BannerInlineNotificationListHostTest : ComposeTest() {

actionButton
.onChildren()
.filterToOne(hasTextExactly(action.title))
.filterToOne(hasTextExactly(action.label))
.assertIsDisplayed()
},
)
Expand Down Expand Up @@ -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<NotificationAction?>(value = null)
Expand All @@ -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()
Expand Down Expand Up @@ -320,8 +320,8 @@ class BannerInlineNotificationListHostTest : ComposeTest() {
title: String,
supportingText: String,
actions: Set<NotificationAction> = setOf(
createFakeNotificationAction(title = "Action 1"),
createFakeNotificationAction(title = "Action 2"),
createFakeNotificationAction(label = "Action 1"),
createFakeNotificationAction(label = "Action 2"),
),
): FakeInAppOnlyNotification {
return FakeInAppOnlyNotification(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,5 @@
<string name="banner_inline_notification_check_error_notifications">Check Error Notifications</string>
<string name="banner_inline_notification_some_messages_need_attention">Some messages need your attention.</string>
<string name="banner_inline_notification_open_notifications">Open notifications</string>
<string name="banner_inline_notification_view_support_article">View support article</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ sealed class PushServiceNotification : AppNotification(), SystemNotification {
private suspend fun buildNotificationActions(): Set<NotificationAction> = 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,
),
)
Loading
Loading