Repo created
This commit is contained in:
parent
75dc487a7a
commit
39c29d175b
6317 changed files with 388324 additions and 2 deletions
19
feature/debug-settings/build.gradle.kts
Normal file
19
feature/debug-settings/build.gradle.kts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
plugins {
|
||||
id(ThunderbirdPlugins.Library.androidCompose)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "net.thunderbird.feature.debug.settings"
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core.ui.compose.designsystem)
|
||||
implementation(projects.core.ui.compose.navigation)
|
||||
implementation(projects.core.common)
|
||||
implementation(projects.core.outcome)
|
||||
implementation(projects.feature.mail.account.api)
|
||||
implementation(projects.feature.notification.api)
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package net.thunderbird.feature.debug.settings
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemesLightDark
|
||||
import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyLarge
|
||||
import app.k9mail.core.ui.compose.theme2.MainTheme
|
||||
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
private fun DebugSectionPreview() {
|
||||
PreviewWithThemesLightDark {
|
||||
Box(modifier = Modifier.padding(MainTheme.spacings.triple)) {
|
||||
DebugSection(
|
||||
title = "Debug section",
|
||||
) {
|
||||
TextBodyLarge("Content")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package net.thunderbird.feature.debug.settings
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import app.k9mail.core.ui.compose.common.koin.koinPreview
|
||||
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemesLightDark
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import net.thunderbird.core.common.resources.StringsResourceManager
|
||||
import net.thunderbird.core.outcome.Outcome
|
||||
import net.thunderbird.feature.debug.settings.notification.DebugNotificationSectionViewModel
|
||||
import net.thunderbird.feature.mail.account.api.AccountManager
|
||||
import net.thunderbird.feature.mail.account.api.BaseAccount
|
||||
import net.thunderbird.feature.notification.api.command.NotificationCommand.Failure
|
||||
import net.thunderbird.feature.notification.api.command.NotificationCommand.Success
|
||||
import net.thunderbird.feature.notification.api.content.Notification
|
||||
import net.thunderbird.feature.notification.api.receiver.InAppNotificationEvent
|
||||
import net.thunderbird.feature.notification.api.receiver.InAppNotificationReceiver
|
||||
import net.thunderbird.feature.notification.api.sender.NotificationSender
|
||||
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
private fun SecretDebugSettingsScreenPreview() {
|
||||
koinPreview {
|
||||
single<DebugNotificationSectionViewModel> {
|
||||
DebugNotificationSectionViewModel(
|
||||
stringsResourceManager = object : StringsResourceManager {
|
||||
override fun stringResource(resourceId: Int): String = "fake"
|
||||
|
||||
override fun stringResource(resourceId: Int, vararg formatArgs: Any?): String = "fake"
|
||||
},
|
||||
accountManager = object : AccountManager<BaseAccount> {
|
||||
override fun getAccounts(): List<BaseAccount> = listOf()
|
||||
override fun getAccountsFlow(): Flow<List<BaseAccount>> = flowOf(listOf())
|
||||
override fun getAccount(accountUuid: String): BaseAccount? = null
|
||||
override fun getAccountFlow(accountUuid: String): Flow<BaseAccount?> = flowOf(null)
|
||||
override fun moveAccount(
|
||||
account: BaseAccount,
|
||||
newPosition: Int,
|
||||
) = Unit
|
||||
|
||||
override fun saveAccount(account: BaseAccount) = Unit
|
||||
},
|
||||
notificationSender = object : NotificationSender {
|
||||
override fun send(
|
||||
notification: Notification,
|
||||
): Flow<Outcome<Success<Notification>, Failure<Notification>>> =
|
||||
error("not implemented")
|
||||
},
|
||||
notificationReceiver = object : InAppNotificationReceiver {
|
||||
override val events: SharedFlow<InAppNotificationEvent>
|
||||
get() = error("not implemented")
|
||||
},
|
||||
)
|
||||
}
|
||||
} WithContent {
|
||||
PreviewWithThemesLightDark {
|
||||
SecretDebugSettingsScreen(
|
||||
onNavigateBack = { },
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package net.thunderbird.feature.debug.settings.inject
|
||||
|
||||
import net.thunderbird.feature.debug.settings.navigation.DefaultSecretDebugSettingsNavigation
|
||||
import net.thunderbird.feature.debug.settings.navigation.SecretDebugSettingsNavigation
|
||||
import net.thunderbird.feature.debug.settings.notification.DebugNotificationSectionViewModel
|
||||
import org.koin.core.module.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
|
||||
val featureDebugSettingsModule = module {
|
||||
single<SecretDebugSettingsNavigation> { DefaultSecretDebugSettingsNavigation() }
|
||||
viewModel {
|
||||
DebugNotificationSectionViewModel(
|
||||
stringsResourceManager = get(),
|
||||
accountManager = get(),
|
||||
notificationSender = get(),
|
||||
notificationReceiver = get(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package net.thunderbird.feature.debug.settings.navigation
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import app.k9mail.core.ui.compose.navigation.deepLinkComposable
|
||||
import net.thunderbird.feature.debug.settings.SecretDebugSettingsScreen
|
||||
import net.thunderbird.feature.debug.settings.navigation.SecretDebugSettingsRoute.Notification
|
||||
|
||||
internal class DefaultSecretDebugSettingsNavigation : SecretDebugSettingsNavigation {
|
||||
override fun registerRoutes(
|
||||
navGraphBuilder: NavGraphBuilder,
|
||||
onBack: () -> Unit,
|
||||
onFinish: (SecretDebugSettingsRoute) -> Unit,
|
||||
) {
|
||||
with(navGraphBuilder) {
|
||||
deepLinkComposable<Notification>(Notification.basePath) {
|
||||
SecretDebugSettingsScreen(
|
||||
onNavigateBack = onBack,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package net.thunderbird.feature.debug.settings.notification
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
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 androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemeLightDark
|
||||
import app.k9mail.core.ui.compose.theme2.MainTheme
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import net.thunderbird.feature.mail.account.api.BaseAccount
|
||||
import net.thunderbird.feature.notification.api.content.MailNotification
|
||||
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
private fun DebugNotificationSectionPreview() {
|
||||
PreviewWithThemeLightDark {
|
||||
val accounts = remember {
|
||||
List(size = 10) {
|
||||
object : BaseAccount {
|
||||
override val uuid: String = Uuid.random().toString()
|
||||
override val name: String? = "Account $it"
|
||||
override val email: String = "account-$it@mail.com"
|
||||
}
|
||||
}.toPersistentList()
|
||||
}
|
||||
var state by remember {
|
||||
mutableStateOf(
|
||||
DebugNotificationSectionContract.State(
|
||||
accounts = accounts,
|
||||
selectedAccount = accounts.first(),
|
||||
),
|
||||
)
|
||||
}
|
||||
DebugNotificationSection(
|
||||
state = state,
|
||||
modifier = Modifier.padding(MainTheme.spacings.triple),
|
||||
onAccountSelect = { state = state.copy(selectedAccount = it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
private fun PreviewSingleMailNotification() {
|
||||
PreviewWithThemeLightDark {
|
||||
val accounts = remember {
|
||||
List(size = 10) {
|
||||
object : BaseAccount {
|
||||
override val uuid: String = Uuid.random().toString()
|
||||
override val name: String? = "Account $it"
|
||||
override val email: String = "account-$it@mail.com"
|
||||
}
|
||||
}.toPersistentList()
|
||||
}
|
||||
var state by remember {
|
||||
mutableStateOf(
|
||||
DebugNotificationSectionContract.State(
|
||||
accounts = accounts,
|
||||
selectedAccount = accounts.first(),
|
||||
selectedSystemNotificationType = MailNotification.NewMailSingleMail::class,
|
||||
),
|
||||
)
|
||||
}
|
||||
DebugNotificationSection(
|
||||
state = state,
|
||||
modifier = Modifier.padding(MainTheme.spacings.triple),
|
||||
onAccountSelect = { state = state.copy(selectedAccount = it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package net.thunderbird.feature.debug.settings
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import app.k9mail.core.ui.compose.designsystem.atom.DividerHorizontal
|
||||
import app.k9mail.core.ui.compose.designsystem.atom.text.TextTitleLarge
|
||||
import app.k9mail.core.ui.compose.designsystem.atom.text.TextTitleMedium
|
||||
import app.k9mail.core.ui.compose.theme2.MainTheme
|
||||
|
||||
@Composable
|
||||
internal fun DebugSection(
|
||||
title: String,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
DebugSection(
|
||||
title = { TextTitleLarge(title) },
|
||||
modifier = modifier,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun DebugSubSection(
|
||||
title: String,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
DebugSection(
|
||||
title = { TextTitleMedium(title) },
|
||||
modifier = modifier,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun DebugSection(
|
||||
title: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
title()
|
||||
DividerHorizontal(modifier = Modifier.padding(vertical = MainTheme.spacings.double))
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package net.thunderbird.feature.debug.settings
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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.template.Scaffold
|
||||
import app.k9mail.core.ui.compose.theme2.MainTheme
|
||||
import net.thunderbird.feature.debug.settings.notification.DebugNotificationSection
|
||||
|
||||
@Composable
|
||||
fun SecretDebugSettingsScreen(
|
||||
onNavigateBack: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = stringResource(R.string.debug_settings_screen_title),
|
||||
navigationIcon = {
|
||||
ButtonIcon(
|
||||
onClick = onNavigateBack,
|
||||
imageVector = Icons.Outlined.ArrowBack,
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
modifier = modifier,
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(MainTheme.spacings.double),
|
||||
) {
|
||||
DebugNotificationSection()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package net.thunderbird.feature.debug.settings.navigation
|
||||
|
||||
import app.k9mail.core.ui.compose.navigation.Navigation
|
||||
|
||||
interface SecretDebugSettingsNavigation : Navigation<SecretDebugSettingsRoute>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package net.thunderbird.feature.debug.settings.navigation
|
||||
|
||||
import app.k9mail.core.ui.compose.navigation.Route
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
sealed interface SecretDebugSettingsRoute : Route {
|
||||
@Serializable
|
||||
data object Notification : SecretDebugSettingsRoute {
|
||||
override val basePath: String = "$SECRET_DEBUG_SETTINGS/notification"
|
||||
|
||||
override fun route(): String = basePath
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SECRET_DEBUG_SETTINGS = "app://secret_debug_settings"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,270 @@
|
|||
package net.thunderbird.feature.debug.settings.notification
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import app.k9mail.core.ui.compose.common.mvi.observeWithoutEffect
|
||||
import app.k9mail.core.ui.compose.designsystem.atom.button.ButtonFilled
|
||||
import app.k9mail.core.ui.compose.designsystem.atom.button.ButtonText
|
||||
import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyMedium
|
||||
import app.k9mail.core.ui.compose.designsystem.molecule.input.SelectInput
|
||||
import app.k9mail.core.ui.compose.designsystem.molecule.input.TextInput
|
||||
import app.k9mail.core.ui.compose.theme2.MainTheme
|
||||
import kotlin.reflect.KClass
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import net.thunderbird.feature.debug.settings.DebugSection
|
||||
import net.thunderbird.feature.debug.settings.DebugSubSection
|
||||
import net.thunderbird.feature.debug.settings.R
|
||||
import net.thunderbird.feature.debug.settings.notification.DebugNotificationSectionContract.Event
|
||||
import net.thunderbird.feature.debug.settings.notification.DebugNotificationSectionContract.ViewModel
|
||||
import net.thunderbird.feature.mail.account.api.BaseAccount
|
||||
import net.thunderbird.feature.notification.api.content.MailNotification
|
||||
import net.thunderbird.feature.notification.api.content.Notification
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
private const val UUID_MAX_CHAR_DISPLAY = 4
|
||||
|
||||
@Composable
|
||||
internal fun DebugNotificationSection(
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: ViewModel = koinViewModel<DebugNotificationSectionViewModel>(),
|
||||
) {
|
||||
val (state, dispatchEvent) = viewModel.observeWithoutEffect()
|
||||
|
||||
DebugNotificationSection(
|
||||
state = state.value,
|
||||
modifier = modifier,
|
||||
onAccountSelect = { account ->
|
||||
dispatchEvent(Event.SelectAccount(account))
|
||||
},
|
||||
onOptionChange = { notificationType ->
|
||||
dispatchEvent(Event.SelectNotificationType(notificationType))
|
||||
},
|
||||
onTriggerSystemNotificationClick = { dispatchEvent(Event.TriggerSystemNotification) },
|
||||
onTriggerInAppNotificationClick = { dispatchEvent(Event.TriggerInAppNotification) },
|
||||
onSenderChange = { dispatchEvent(Event.OnSenderChange(it)) },
|
||||
onSubjectChange = { dispatchEvent(Event.OnSubjectChange(it)) },
|
||||
onSummaryChange = { dispatchEvent(Event.OnSummaryChange(it)) },
|
||||
onPreviewChange = { dispatchEvent(Event.OnPreviewChange(it)) },
|
||||
onClearStatusLog = { dispatchEvent(Event.ClearStatusLog) },
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun DebugNotificationSection(
|
||||
state: DebugNotificationSectionContract.State,
|
||||
modifier: Modifier = Modifier,
|
||||
onAccountSelect: (BaseAccount) -> Unit = {},
|
||||
onOptionChange: (KClass<out Notification>) -> Unit = {},
|
||||
onTriggerSystemNotificationClick: () -> Unit = {},
|
||||
onTriggerInAppNotificationClick: () -> Unit = {},
|
||||
onSenderChange: (String) -> Unit = {},
|
||||
onSubjectChange: (String) -> Unit = {},
|
||||
onSummaryChange: (String) -> Unit = {},
|
||||
onPreviewChange: (String) -> Unit = {},
|
||||
onClearStatusLog: () -> Unit = {},
|
||||
) {
|
||||
DebugSection(
|
||||
title = stringResource(R.string.debug_settings_notifications_title),
|
||||
modifier = modifier,
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.quadruple),
|
||||
) {
|
||||
CommonNotificationInformation(state, onAccountSelect)
|
||||
SystemNotificationSection(
|
||||
state = state,
|
||||
onOptionChange = onOptionChange,
|
||||
onClick = onTriggerSystemNotificationClick,
|
||||
onSenderChange = onSenderChange,
|
||||
onSubjectChange = onSubjectChange,
|
||||
onSummaryChange = onSummaryChange,
|
||||
onPreviewChange = onPreviewChange,
|
||||
)
|
||||
InAppNotificationSection(
|
||||
selectedNotificationType = state.selectedInAppNotificationType,
|
||||
options = state.inAppNotificationTypes,
|
||||
onOptionChange = onOptionChange,
|
||||
onClick = onTriggerInAppNotificationClick,
|
||||
)
|
||||
NotificationStatusLog(state.notificationStatusLog, onClearStatusLog)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CommonNotificationInformation(
|
||||
state: DebugNotificationSectionContract.State,
|
||||
onAccountSelect: (BaseAccount) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
DebugSubSection(
|
||||
title = stringResource(R.string.debug_settings_notifications_common_notification_information),
|
||||
modifier = modifier.padding(start = MainTheme.spacings.double),
|
||||
) {
|
||||
val loadingText = stringResource(R.string.debug_settings_notifications_loading)
|
||||
SelectInput(
|
||||
options = state.accounts,
|
||||
selectedOption = state.selectedAccount,
|
||||
onOptionChange = { account ->
|
||||
account?.let(onAccountSelect)
|
||||
},
|
||||
optionToStringTransformation = { account ->
|
||||
account?.let { account ->
|
||||
val uuidStart = account.uuid.take(UUID_MAX_CHAR_DISPLAY)
|
||||
val uuidEnd = account.uuid.take(UUID_MAX_CHAR_DISPLAY)
|
||||
val accountDisplay = account.name ?: account.email
|
||||
"$uuidStart..$uuidEnd - $accountDisplay"
|
||||
} ?: loadingText
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SystemNotificationSection(
|
||||
state: DebugNotificationSectionContract.State,
|
||||
onOptionChange: (KClass<out Notification>) -> Unit,
|
||||
onClick: () -> Unit,
|
||||
onSenderChange: (String) -> Unit,
|
||||
onSubjectChange: (String) -> Unit,
|
||||
onSummaryChange: (String) -> Unit,
|
||||
onPreviewChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
DebugSubSection(
|
||||
title = stringResource(R.string.debug_settings_notifications_system_notification),
|
||||
modifier = modifier.padding(start = MainTheme.spacings.double),
|
||||
) {
|
||||
Column {
|
||||
TriggerNotificationSection(
|
||||
selectedNotificationType = state.selectedSystemNotificationType,
|
||||
options = state.systemNotificationTypes,
|
||||
onOptionChange = onOptionChange,
|
||||
onClick = onClick,
|
||||
)
|
||||
AnimatedVisibility(state.selectedSystemNotificationType == MailNotification.NewMailSingleMail::class) {
|
||||
Column {
|
||||
TextInput(
|
||||
onTextChange = onSenderChange,
|
||||
text = state.singleNotificationData.sender,
|
||||
label = stringResource(R.string.debug_settings_notifications_single_mail_sender),
|
||||
)
|
||||
TextInput(
|
||||
onTextChange = onSubjectChange,
|
||||
text = state.singleNotificationData.subject,
|
||||
label = stringResource(R.string.debug_settings_notifications_single_mail_subject),
|
||||
)
|
||||
TextInput(
|
||||
onTextChange = onSummaryChange,
|
||||
text = state.singleNotificationData.summary,
|
||||
label = stringResource(R.string.debug_settings_notifications_single_mail_summary),
|
||||
)
|
||||
TextInput(
|
||||
onTextChange = onPreviewChange,
|
||||
text = state.singleNotificationData.preview,
|
||||
label = stringResource(R.string.debug_settings_notifications_single_mail_preview),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InAppNotificationSection(
|
||||
selectedNotificationType: KClass<out Notification>?,
|
||||
options: ImmutableList<KClass<out Notification>>,
|
||||
onOptionChange: (KClass<out Notification>) -> Unit,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
DebugSubSection(
|
||||
title = stringResource(R.string.debug_settings_notifications_in_app_notification),
|
||||
modifier = modifier.padding(start = MainTheme.spacings.double),
|
||||
) {
|
||||
TriggerNotificationSection(
|
||||
selectedNotificationType = selectedNotificationType,
|
||||
options = options,
|
||||
onOptionChange = onOptionChange,
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TriggerNotificationSection(
|
||||
selectedNotificationType: KClass<out Notification>?,
|
||||
options: ImmutableList<KClass<out Notification>>,
|
||||
onOptionChange: (KClass<out Notification>) -> Unit,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.oneHalf),
|
||||
modifier = modifier,
|
||||
) {
|
||||
val selectedOption = remember(selectedNotificationType, options) {
|
||||
selectedNotificationType ?: options.firstOrNull()
|
||||
}
|
||||
val loadingText = stringResource(R.string.debug_settings_notifications_loading)
|
||||
SelectInput(
|
||||
options = options,
|
||||
selectedOption = selectedOption,
|
||||
onOptionChange = { it?.let(onOptionChange) },
|
||||
optionToStringTransformation = { kClass -> kClass?.realName ?: loadingText },
|
||||
)
|
||||
|
||||
ButtonFilled(
|
||||
text = stringResource(R.string.debug_settings_notifications_trigger_notification),
|
||||
onClick = onClick,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ColumnScope.NotificationStatusLog(
|
||||
notificationStatusLog: ImmutableList<String>,
|
||||
onClearStatusLog: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = notificationStatusLog.isNotEmpty(),
|
||||
modifier = modifier.padding(start = MainTheme.spacings.double),
|
||||
) {
|
||||
DebugSubSection(
|
||||
title = stringResource(R.string.debug_settings_notification_status_log),
|
||||
) {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
TextBodyMedium(
|
||||
text = buildAnnotatedString {
|
||||
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||
appendLine(stringResource(R.string.debug_settings_notifications_status))
|
||||
}
|
||||
notificationStatusLog.forEach { status ->
|
||||
appendLine(status)
|
||||
}
|
||||
},
|
||||
)
|
||||
ButtonText(
|
||||
text = stringResource(R.string.debug_settings_notifications_clear_status_log),
|
||||
onClick = onClearStatusLog,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package net.thunderbird.feature.debug.settings.notification
|
||||
|
||||
import app.k9mail.core.ui.compose.common.mvi.UnidirectionalViewModel
|
||||
import kotlin.reflect.KClass
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import net.thunderbird.feature.mail.account.api.BaseAccount
|
||||
import net.thunderbird.feature.notification.api.content.Notification
|
||||
|
||||
internal interface DebugNotificationSectionContract {
|
||||
|
||||
interface ViewModel : UnidirectionalViewModel<State, Event, Effect>
|
||||
|
||||
data class State(
|
||||
val accounts: ImmutableList<BaseAccount> = persistentListOf(),
|
||||
val selectedAccount: BaseAccount? = null,
|
||||
val notificationStatusLog: ImmutableList<String> = persistentListOf("Ready to send notification"),
|
||||
val selectedSystemNotificationType: KClass<out Notification>? = null,
|
||||
val selectedInAppNotificationType: KClass<out Notification>? = null,
|
||||
val folderName: String? = null,
|
||||
val singleNotificationData: MailSingleNotificationData = MailSingleNotificationData.Undefined,
|
||||
val systemNotificationTypes: ImmutableList<KClass<out Notification>> = persistentListOf(),
|
||||
val inAppNotificationTypes: ImmutableList<KClass<out Notification>> = persistentListOf(),
|
||||
) {
|
||||
data class MailSingleNotificationData(
|
||||
val sender: String = "",
|
||||
val subject: String = "",
|
||||
val summary: String = "",
|
||||
val preview: String = "",
|
||||
) {
|
||||
companion object {
|
||||
val Undefined = MailSingleNotificationData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface Event {
|
||||
data class SelectAccount(val account: BaseAccount) : Event
|
||||
data class SelectNotificationType(val notificationType: KClass<out Notification>) : Event
|
||||
data object TriggerSystemNotification : Event
|
||||
data object TriggerInAppNotification : Event
|
||||
data class OnSenderChange(val sender: String) : Event
|
||||
data class OnSubjectChange(val subject: String) : Event
|
||||
data class OnSummaryChange(val summary: String) : Event
|
||||
data class OnPreviewChange(val preview: String) : Event
|
||||
data object ClearStatusLog : Event
|
||||
}
|
||||
|
||||
sealed interface Effect
|
||||
}
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
package net.thunderbird.feature.debug.settings.notification
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.k9mail.core.ui.compose.common.mvi.BaseViewModel
|
||||
import kotlin.reflect.KClass
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.thunderbird.core.common.resources.StringsResourceManager
|
||||
import net.thunderbird.feature.debug.settings.R
|
||||
import net.thunderbird.feature.debug.settings.notification.DebugNotificationSectionContract.Effect
|
||||
import net.thunderbird.feature.debug.settings.notification.DebugNotificationSectionContract.Event
|
||||
import net.thunderbird.feature.debug.settings.notification.DebugNotificationSectionContract.State
|
||||
import net.thunderbird.feature.mail.account.api.AccountManager
|
||||
import net.thunderbird.feature.mail.account.api.BaseAccount
|
||||
import net.thunderbird.feature.notification.api.NotificationGroup
|
||||
import net.thunderbird.feature.notification.api.NotificationGroupKey
|
||||
import net.thunderbird.feature.notification.api.content.AuthenticationErrorNotification
|
||||
import net.thunderbird.feature.notification.api.content.CertificateErrorNotification
|
||||
import net.thunderbird.feature.notification.api.content.FailedToCreateNotification
|
||||
import net.thunderbird.feature.notification.api.content.InAppNotification
|
||||
import net.thunderbird.feature.notification.api.content.MailNotification
|
||||
import net.thunderbird.feature.notification.api.content.Notification
|
||||
import net.thunderbird.feature.notification.api.content.PushServiceNotification
|
||||
import net.thunderbird.feature.notification.api.content.SystemNotification
|
||||
import net.thunderbird.feature.notification.api.receiver.InAppNotificationReceiver
|
||||
import net.thunderbird.feature.notification.api.sender.NotificationSender
|
||||
|
||||
internal class DebugNotificationSectionViewModel(
|
||||
private val stringsResourceManager: StringsResourceManager,
|
||||
private val accountManager: AccountManager<BaseAccount>,
|
||||
private val notificationSender: NotificationSender,
|
||||
private val notificationReceiver: InAppNotificationReceiver,
|
||||
private val mainDispatcher: CoroutineDispatcher = Dispatchers.Main,
|
||||
ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
|
||||
) : BaseViewModel<State, Event, Effect>(initialState = State()), DebugNotificationSectionContract.ViewModel {
|
||||
|
||||
init {
|
||||
viewModelScope.launch(ioDispatcher) {
|
||||
val accounts = accountManager.getAccounts()
|
||||
withContext(mainDispatcher) {
|
||||
updateState {
|
||||
val systemNotificationTypes = buildList {
|
||||
add(AuthenticationErrorNotification::class)
|
||||
add(CertificateErrorNotification::class)
|
||||
add(FailedToCreateNotification::class)
|
||||
add(MailNotification.Fetching::class)
|
||||
add(MailNotification.NewMailSingleMail::class)
|
||||
add(MailNotification.NewMailSummaryMail::class)
|
||||
add(MailNotification.SendFailed::class)
|
||||
add(MailNotification.Sending::class)
|
||||
add(PushServiceNotification.AlarmPermissionMissing::class)
|
||||
add(PushServiceNotification.Initializing::class)
|
||||
add(PushServiceNotification.Listening::class)
|
||||
add(PushServiceNotification.WaitBackgroundSync::class)
|
||||
add(PushServiceNotification.WaitNetwork::class)
|
||||
}.toPersistentList()
|
||||
|
||||
val inAppNotificationTypes = buildList {
|
||||
add(AuthenticationErrorNotification::class)
|
||||
add(CertificateErrorNotification::class)
|
||||
add(FailedToCreateNotification::class)
|
||||
add(MailNotification.SendFailed::class)
|
||||
add(PushServiceNotification.AlarmPermissionMissing::class)
|
||||
}.toPersistentList()
|
||||
State(
|
||||
accounts = accounts.toPersistentList(),
|
||||
selectedAccount = accounts.first(),
|
||||
systemNotificationTypes = systemNotificationTypes,
|
||||
inAppNotificationTypes = inAppNotificationTypes,
|
||||
selectedSystemNotificationType = systemNotificationTypes.first(),
|
||||
selectedInAppNotificationType = inAppNotificationTypes.first(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
notificationReceiver
|
||||
.events
|
||||
.collectLatest { event ->
|
||||
updateState { state ->
|
||||
state.copy(
|
||||
notificationStatusLog = state.notificationStatusLog + " In-app notification event: $event",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun event(event: Event) {
|
||||
when (event) {
|
||||
is Event.TriggerSystemNotification -> viewModelScope.launch {
|
||||
if (state.value.selectedSystemNotificationType == null) {
|
||||
updateState {
|
||||
it.copy(selectedSystemNotificationType = state.value.systemNotificationTypes.first())
|
||||
}
|
||||
}
|
||||
triggerNotification(
|
||||
notification = requireNotNull(buildNotification(state.value.selectedSystemNotificationType)),
|
||||
)
|
||||
}
|
||||
|
||||
is Event.TriggerInAppNotification -> viewModelScope.launch {
|
||||
if (state.value.selectedInAppNotificationType == null) {
|
||||
updateState {
|
||||
it.copy(selectedInAppNotificationType = state.value.inAppNotificationTypes.first())
|
||||
}
|
||||
}
|
||||
triggerNotification(
|
||||
notification = requireNotNull(buildNotification(state.value.selectedInAppNotificationType)),
|
||||
)
|
||||
}
|
||||
|
||||
is Event.SelectAccount -> updateState { state ->
|
||||
state.copy(selectedAccount = event.account)
|
||||
}
|
||||
|
||||
is Event.SelectNotificationType -> viewModelScope.launch {
|
||||
buildNotification(event.notificationType)
|
||||
}
|
||||
|
||||
is Event.OnSenderChange -> updateState {
|
||||
it.copy(singleNotificationData = it.singleNotificationData.copy(sender = event.sender))
|
||||
}
|
||||
|
||||
is Event.OnSubjectChange -> updateState {
|
||||
it.copy(singleNotificationData = it.singleNotificationData.copy(subject = event.subject))
|
||||
}
|
||||
|
||||
is Event.OnSummaryChange -> updateState {
|
||||
it.copy(singleNotificationData = it.singleNotificationData.copy(summary = event.summary))
|
||||
}
|
||||
|
||||
is Event.OnPreviewChange -> updateState {
|
||||
it.copy(singleNotificationData = it.singleNotificationData.copy(preview = event.preview))
|
||||
}
|
||||
|
||||
Event.ClearStatusLog -> updateState { it.copy(notificationStatusLog = persistentListOf()) }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun triggerNotification(
|
||||
notification: Notification,
|
||||
) {
|
||||
notification.let { notification ->
|
||||
notificationSender
|
||||
.send(notification)
|
||||
.collect { result ->
|
||||
updateState {
|
||||
it.copy(notificationStatusLog = it.notificationStatusLog + "Result: $result")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun buildNotification(notificationType: KClass<out Notification>?): Notification? {
|
||||
updateState {
|
||||
it.copy(
|
||||
notificationStatusLog = it.notificationStatusLog +
|
||||
stringsResourceManager.stringResource(
|
||||
R.string.debug_settings_notifications_preparing_notification,
|
||||
notificationType?.realName,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
val state = state.value
|
||||
val selectedAccount = state.selectedAccount ?: return null
|
||||
val accountDisplay = selectedAccount.name ?: selectedAccount.email
|
||||
|
||||
val notification = buildNotification(
|
||||
notificationType = notificationType,
|
||||
selectedAccount = selectedAccount,
|
||||
accountDisplay = accountDisplay,
|
||||
state = state,
|
||||
)
|
||||
|
||||
updateState { state ->
|
||||
state.copy(
|
||||
selectedSystemNotificationType = (notification as? SystemNotification)?.let { it::class }
|
||||
?: state.selectedSystemNotificationType,
|
||||
selectedInAppNotificationType = (notification as? InAppNotification)?.let { it::class }
|
||||
?: state.selectedInAppNotificationType,
|
||||
)
|
||||
}
|
||||
|
||||
return notification
|
||||
}
|
||||
|
||||
@Suppress("CyclomaticComplexMethod", "LongMethod")
|
||||
private suspend fun buildNotification(
|
||||
notificationType: KClass<out Notification>?,
|
||||
selectedAccount: BaseAccount,
|
||||
accountDisplay: String,
|
||||
state: State,
|
||||
): Notification? = when (notificationType) {
|
||||
AuthenticationErrorNotification::class -> AuthenticationErrorNotification(
|
||||
accountUuid = selectedAccount.uuid,
|
||||
accountDisplayName = accountDisplay,
|
||||
)
|
||||
|
||||
CertificateErrorNotification::class -> CertificateErrorNotification(
|
||||
accountUuid = selectedAccount.uuid,
|
||||
accountDisplayName = accountDisplay,
|
||||
)
|
||||
|
||||
FailedToCreateNotification::class -> FailedToCreateNotification(
|
||||
accountUuid = selectedAccount.uuid,
|
||||
failedNotification = AuthenticationErrorNotification(
|
||||
accountUuid = selectedAccount.uuid,
|
||||
accountDisplayName = accountDisplay,
|
||||
),
|
||||
)
|
||||
|
||||
MailNotification.Fetching::class -> MailNotification.Fetching(
|
||||
accountUuid = selectedAccount.uuid,
|
||||
accountDisplayName = accountDisplay,
|
||||
folderName = state.folderName,
|
||||
)
|
||||
|
||||
MailNotification.NewMailSingleMail::class -> state.buildSingleMailNotification(
|
||||
selectedAccount = selectedAccount,
|
||||
accountDisplay = accountDisplay,
|
||||
)
|
||||
|
||||
MailNotification.NewMailSummaryMail::class -> MailNotification.NewMailSummaryMail(
|
||||
accountUuid = selectedAccount.uuid,
|
||||
accountDisplayName = accountDisplay,
|
||||
messagesNotificationChannelSuffix = "",
|
||||
newMessageCount = 10,
|
||||
additionalMessagesCount = 10,
|
||||
group = NotificationGroup(
|
||||
key = NotificationGroupKey("key"),
|
||||
summary = "",
|
||||
),
|
||||
)
|
||||
|
||||
MailNotification.SendFailed::class -> MailNotification.SendFailed(
|
||||
accountUuid = selectedAccount.uuid,
|
||||
exception = Exception("What a failure"),
|
||||
)
|
||||
|
||||
MailNotification.Sending::class -> MailNotification.Sending(
|
||||
accountUuid = selectedAccount.uuid,
|
||||
accountDisplayName = accountDisplay,
|
||||
)
|
||||
|
||||
PushServiceNotification.AlarmPermissionMissing::class -> PushServiceNotification.AlarmPermissionMissing()
|
||||
|
||||
PushServiceNotification.Initializing::class -> PushServiceNotification.Initializing()
|
||||
|
||||
PushServiceNotification.Listening::class -> PushServiceNotification.Listening()
|
||||
|
||||
PushServiceNotification.WaitBackgroundSync::class -> PushServiceNotification.WaitBackgroundSync()
|
||||
|
||||
PushServiceNotification.WaitNetwork::class -> PushServiceNotification.WaitNetwork()
|
||||
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun State.buildSingleMailNotification(
|
||||
selectedAccount: BaseAccount,
|
||||
accountDisplay: String,
|
||||
): MailNotification.NewMailSingleMail? = MailNotification.NewMailSingleMail(
|
||||
accountUuid = selectedAccount.uuid,
|
||||
accountName = accountDisplay,
|
||||
messagesNotificationChannelSuffix = "",
|
||||
summary = singleNotificationData.summary,
|
||||
sender = singleNotificationData.sender,
|
||||
subject = singleNotificationData.subject,
|
||||
preview = singleNotificationData.preview,
|
||||
group = null,
|
||||
)
|
||||
|
||||
private operator fun ImmutableList<String>.plus(other: String): ImmutableList<String> =
|
||||
(this.toMutableList() + other).toPersistentList()
|
||||
}
|
||||
|
||||
internal val KClass<out Notification>.realName: String
|
||||
get() {
|
||||
val clazz = java
|
||||
|
||||
return clazz.name
|
||||
.replace(clazz.`package`?.name.orEmpty(), "")
|
||||
.removePrefix(".")
|
||||
.replace("$", ".")
|
||||
}
|
||||
18
feature/debug-settings/src/main/res/values/strings.xml
Normal file
18
feature/debug-settings/src/main/res/values/strings.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="debug_settings_screen_title">Secret Debug Settings Screen</string>
|
||||
<string name="debug_settings_notifications_loading">Loading…</string>
|
||||
<string name="debug_settings_notifications_trigger_notification">Trigger notification</string>
|
||||
<string name="debug_settings_notifications_single_mail_sender">Sender</string>
|
||||
<string name="debug_settings_notifications_single_mail_subject">Subject</string>
|
||||
<string name="debug_settings_notifications_single_mail_summary">Summary</string>
|
||||
<string name="debug_settings_notifications_single_mail_preview">Preview</string>
|
||||
<string name="debug_settings_notifications_common_notification_information">Common notification information</string>
|
||||
<string name="debug_settings_notifications_in_app_notification">In-App notification</string>
|
||||
<string name="debug_settings_notifications_title">Notifications</string>
|
||||
<string name="debug_settings_notifications_system_notification">System notification</string>
|
||||
<string name="debug_settings_notifications_preparing_notification">Preparing notification %1$s</string>
|
||||
<string name="debug_settings_notification_status_log">Notification status log</string>
|
||||
<string name="debug_settings_notifications_status">"Status: "</string>
|
||||
<string name="debug_settings_notifications_clear_status_log">Clear status log</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package net.thunderbird.feature.debug.settings.inject
|
||||
|
||||
import net.thunderbird.feature.debug.settings.navigation.NoOpSecretDebugSettingsNavigation
|
||||
import net.thunderbird.feature.debug.settings.navigation.SecretDebugSettingsNavigation
|
||||
import org.koin.dsl.module
|
||||
|
||||
val featureDebugSettingsModule = module {
|
||||
single<SecretDebugSettingsNavigation> { NoOpSecretDebugSettingsNavigation }
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package net.thunderbird.feature.debug.settings.navigation
|
||||
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
|
||||
object NoOpSecretDebugSettingsNavigation : SecretDebugSettingsNavigation {
|
||||
override fun registerRoutes(
|
||||
navGraphBuilder: NavGraphBuilder,
|
||||
onBack: () -> Unit,
|
||||
onFinish: (SecretDebugSettingsRoute) -> Unit,
|
||||
) = Unit
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue