diff --git a/feature/account/oauth/build.gradle.kts b/feature/account/oauth/build.gradle.kts index 73927f307cd..42625b6f840 100644 --- a/feature/account/oauth/build.gradle.kts +++ b/feature/account/oauth/build.gradle.kts @@ -24,6 +24,7 @@ dependencies { implementation(libs.appauth) implementation(libs.androidx.compose.material3) + testImplementation(projects.core.logging.testing) testImplementation(projects.core.ui.compose.testing) } diff --git a/feature/account/oauth/src/main/kotlin/app/k9mail/feature/account/oauth/AccountOAuthModule.kt b/feature/account/oauth/src/main/kotlin/app/k9mail/feature/account/oauth/AccountOAuthModule.kt index 94288cced75..1284d30b74e 100644 --- a/feature/account/oauth/src/main/kotlin/app/k9mail/feature/account/oauth/AccountOAuthModule.kt +++ b/feature/account/oauth/src/main/kotlin/app/k9mail/feature/account/oauth/AccountOAuthModule.kt @@ -50,6 +50,7 @@ val featureAccountOAuthModule: Module = module { getOAuthRequestIntent = get(), finishOAuthSignIn = get(), checkIsGoogleSignIn = get(), + logger = get(), ) } } diff --git a/feature/account/oauth/src/main/kotlin/app/k9mail/feature/account/oauth/domain/entity/AuthorizationIntentResult.kt b/feature/account/oauth/src/main/kotlin/app/k9mail/feature/account/oauth/domain/entity/AuthorizationIntentResult.kt index 2abc7379ab3..b15ac123db2 100644 --- a/feature/account/oauth/src/main/kotlin/app/k9mail/feature/account/oauth/domain/entity/AuthorizationIntentResult.kt +++ b/feature/account/oauth/src/main/kotlin/app/k9mail/feature/account/oauth/domain/entity/AuthorizationIntentResult.kt @@ -4,6 +4,7 @@ import android.content.Intent sealed interface AuthorizationIntentResult { object NotSupported : AuthorizationIntentResult + object BrowserNotAvailable : AuthorizationIntentResult data class Success( val intent: Intent, diff --git a/feature/account/oauth/src/main/kotlin/app/k9mail/feature/account/oauth/ui/AccountOAuthViewModel.kt b/feature/account/oauth/src/main/kotlin/app/k9mail/feature/account/oauth/ui/AccountOAuthViewModel.kt index a6daad7bb43..ffc3b875f47 100644 --- a/feature/account/oauth/src/main/kotlin/app/k9mail/feature/account/oauth/ui/AccountOAuthViewModel.kt +++ b/feature/account/oauth/src/main/kotlin/app/k9mail/feature/account/oauth/ui/AccountOAuthViewModel.kt @@ -1,6 +1,7 @@ package app.k9mail.feature.account.oauth.ui import android.app.Activity +import android.content.ActivityNotFoundException import android.content.Intent import androidx.lifecycle.viewModelScope import app.k9mail.feature.account.common.domain.entity.AuthorizationState @@ -13,6 +14,7 @@ import app.k9mail.feature.account.oauth.ui.AccountOAuthContract.Event import app.k9mail.feature.account.oauth.ui.AccountOAuthContract.State import app.k9mail.feature.account.oauth.ui.AccountOAuthContract.ViewModel import kotlinx.coroutines.launch +import net.thunderbird.core.logging.Logger import net.thunderbird.core.ui.contract.mvi.BaseViewModel class AccountOAuthViewModel( @@ -20,6 +22,7 @@ class AccountOAuthViewModel( private val getOAuthRequestIntent: UseCase.GetOAuthRequestIntent, private val finishOAuthSignIn: UseCase.FinishOAuthSignIn, private val checkIsGoogleSignIn: UseCase.CheckIsGoogleSignIn, + private val logger: Logger, ) : BaseViewModel(initialState), ViewModel { override fun initState(state: State) { @@ -45,10 +48,15 @@ class AccountOAuthViewModel( } private fun onSignIn() { - val result = getOAuthRequestIntent.execute( - hostname = state.value.hostname, - emailAddress = state.value.emailAddress, - ) + val result = try { + getOAuthRequestIntent.execute( + hostname = state.value.hostname, + emailAddress = state.value.emailAddress, + ) + } catch (e: ActivityNotFoundException) { + logger.error(throwable = e) { "Failed to launch custom tabs. Browser is not available." } + AuthorizationIntentResult.BrowserNotAvailable + } when (result) { AuthorizationIntentResult.NotSupported -> { @@ -59,6 +67,9 @@ class AccountOAuthViewModel( } } + AuthorizationIntentResult.BrowserNotAvailable -> + updateErrorState(Error.BrowserNotAvailable) + is AuthorizationIntentResult.Success -> { emitEffect(Effect.LaunchOAuth(result.intent)) } diff --git a/feature/account/oauth/src/test/kotlin/app/k9mail/feature/account/oauth/ui/AccountOAuthViewModelTest.kt b/feature/account/oauth/src/test/kotlin/app/k9mail/feature/account/oauth/ui/AccountOAuthViewModelTest.kt index 53a1e1446ef..20bd34b994b 100644 --- a/feature/account/oauth/src/test/kotlin/app/k9mail/feature/account/oauth/ui/AccountOAuthViewModelTest.kt +++ b/feature/account/oauth/src/test/kotlin/app/k9mail/feature/account/oauth/ui/AccountOAuthViewModelTest.kt @@ -1,10 +1,12 @@ package app.k9mail.feature.account.oauth.ui import android.app.Activity +import android.content.ActivityNotFoundException import android.content.Intent import app.k9mail.core.ui.compose.testing.mvi.runMviTest import app.k9mail.core.ui.compose.testing.mvi.turbinesWithInitialStateCheck import app.k9mail.feature.account.common.domain.entity.AuthorizationState +import app.k9mail.feature.account.oauth.domain.AccountOAuthDomainContract.UseCase import app.k9mail.feature.account.oauth.domain.entity.AuthorizationIntentResult import app.k9mail.feature.account.oauth.domain.entity.AuthorizationResult import app.k9mail.feature.account.oauth.ui.AccountOAuthContract.Effect @@ -19,6 +21,7 @@ import kotlin.test.Test import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.test.UnconfinedTestDispatcher +import net.thunderbird.core.logging.testing.TestLogger import net.thunderbird.core.testing.coroutines.MainDispatcherHelper import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner @@ -159,6 +162,24 @@ class AccountOAuthViewModelTest { ) } + @Test + fun `should set error state when onOAuthResult received with BrowserNotAvailable`() = runMviTest { + val initialState = defaultState + val testSubject = createTestSubject( + getOAuthRequestIntent = { _, _ -> throw ActivityNotFoundException("browser not available") }, + initialState = initialState, + ) + val turbines = turbinesWithInitialStateCheck(testSubject, initialState) + + testSubject.event(Event.SignInClicked) + + assertThat(turbines.stateTurbine.awaitItem()).isEqualTo( + initialState.copy( + error = Error.BrowserNotAvailable, + ), + ) + } + @Test fun `should finish OAuth sign in when onOAuthResult received with success but authorization result is cancelled`() = runMviTest { @@ -231,10 +252,20 @@ class AccountOAuthViewModelTest { authorizationResult: AuthorizationResult = AuthorizationResult.Success(AuthorizationState()), isGoogleSignIn: Boolean = false, initialState: State = State(), + ) = createTestSubject( + getOAuthRequestIntent = { _, _ -> authorizationIntentResult }, + authorizationResult = authorizationResult, + isGoogleSignIn = isGoogleSignIn, + initialState = initialState, + ) + + fun createTestSubject( + getOAuthRequestIntent: UseCase.GetOAuthRequestIntent, + authorizationResult: AuthorizationResult = AuthorizationResult.Success(AuthorizationState()), + isGoogleSignIn: Boolean = false, + initialState: State = State(), ) = AccountOAuthViewModel( - getOAuthRequestIntent = { _, _ -> - authorizationIntentResult - }, + getOAuthRequestIntent = getOAuthRequestIntent, finishOAuthSignIn = { _ -> delay(50) authorizationResult @@ -243,6 +274,7 @@ class AccountOAuthViewModelTest { isGoogleSignIn }, initialState = initialState, + logger = TestLogger(), ) } } diff --git a/feature/launcher/src/main/kotlin/app/k9mail/feature/launcher/navigation/FeatureLauncherNavHost.kt b/feature/launcher/src/main/kotlin/app/k9mail/feature/launcher/navigation/FeatureLauncherNavHost.kt index 107a7013213..3b937ebc3c1 100644 --- a/feature/launcher/src/main/kotlin/app/k9mail/feature/launcher/navigation/FeatureLauncherNavHost.kt +++ b/feature/launcher/src/main/kotlin/app/k9mail/feature/launcher/navigation/FeatureLauncherNavHost.kt @@ -63,7 +63,7 @@ fun FeatureLauncherNavHost( thundermailNavigation.registerRoutes( navGraphBuilder = this, - onBack = onBack, + onBack = { navController.popBackStack() }, onFinish = { route -> when (route) { is ThundermailRoute.IncomingSettings -> diff --git a/feature/settings/import/src/main/kotlin/app/k9mail/feature/settings/import/ui/AuthViewModel.kt b/feature/settings/import/src/main/kotlin/app/k9mail/feature/settings/import/ui/AuthViewModel.kt index 6c3341f7fed..373acde4888 100644 --- a/feature/settings/import/src/main/kotlin/app/k9mail/feature/settings/import/ui/AuthViewModel.kt +++ b/feature/settings/import/src/main/kotlin/app/k9mail/feature/settings/import/ui/AuthViewModel.kt @@ -92,6 +92,8 @@ internal class AuthViewModel( _uiState.update { AuthFlowState.NotSupported } } + AuthorizationIntentResult.BrowserNotAvailable -> _uiState.update { AuthFlowState.BrowserNotFound } + is AuthorizationIntentResult.Success -> resultObserver.login(authRequestIntentResult.intent) } } diff --git a/feature/thundermail/internal/common/src/debug/kotlin/net/thunderbird/feature/thundermail/internal/common/ui/ThundermailOAuthRedirectScreenPreview.kt b/feature/thundermail/internal/common/src/debug/kotlin/net/thunderbird/feature/thundermail/internal/common/ui/ThundermailOAuthRedirectScreenPreview.kt new file mode 100644 index 00000000000..2b4de130d4f --- /dev/null +++ b/feature/thundermail/internal/common/src/debug/kotlin/net/thunderbird/feature/thundermail/internal/common/ui/ThundermailOAuthRedirectScreenPreview.kt @@ -0,0 +1,43 @@ +package net.thunderbird.feature.thundermail.internal.common.ui + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import app.k9mail.core.ui.compose.designsystem.PreviewWithThemeLightDark + +@Preview +@Composable +private fun ThundermailOAuthRedirectScreenPreview( + @PreviewParameter(ThundermailOAuthRedirectScreenPreviewColProvider::class) param: + Pair, +) { + val (_, state) = param + PreviewWithThemeLightDark { + ThundermailOAuthRedirectScreen( + state = state, + onBack = {}, + modifier = Modifier.fillMaxSize(), + ) + } +} + +private class ThundermailOAuthRedirectScreenPreviewColProvider : + CollectionPreviewParameterProvider>( + listOf( + "Default" to ThundermailContract.State(), + "Browser not available error" to ThundermailContract.State( + error = ThundermailContract.Error.BrowserNotAvailable, + ), + "Canceled error" to ThundermailContract.State( + error = ThundermailContract.Error.Canceled, + ), + "Unknown error" to ThundermailContract.State( + error = ThundermailContract.Error.Unknown(Exception("Something went wrong")), + ), + ), + ) { + override fun getDisplayName(index: Int): String = values.elementAt(index).first +} diff --git a/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/inject/FeatureThundermailCommonModule.kt b/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/inject/FeatureThundermailCommonModule.kt index f7d29fa110c..18757da909c 100644 --- a/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/inject/FeatureThundermailCommonModule.kt +++ b/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/inject/FeatureThundermailCommonModule.kt @@ -15,6 +15,7 @@ val featureThundermailCommonModule = module { getOAuthRequestIntent = get(), finishOAuthSignIn = get(), checkIsGoogleSignIn = get(), + logger = get(), ) } viewModel { diff --git a/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/navigation/DefaultThundermailNavigation.kt b/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/navigation/DefaultThundermailNavigation.kt index 51d84d26160..5d522499a48 100644 --- a/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/navigation/DefaultThundermailNavigation.kt +++ b/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/navigation/DefaultThundermailNavigation.kt @@ -1,39 +1,16 @@ package net.thunderbird.feature.thundermail.internal.common.navigation -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.safeContentPadding -import androidx.compose.foundation.layout.size -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.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.navigation.NavGraphBuilder -import app.k9mail.core.ui.compose.designsystem.atom.CircularProgressIndicator -import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyLarge import app.k9mail.feature.account.setup.navigation.AccountSetupNavHost import app.k9mail.feature.account.setup.navigation.AccountSetupRoute import app.k9mail.feature.onboarding.permissions.ui.PermissionsScreen import app.k9mail.feature.settings.import.ui.SettingsImportAction import app.k9mail.feature.settings.import.ui.SettingsImportScreen -import net.thunderbird.core.ui.compose.theme2.MainTheme -import net.thunderbird.core.ui.contract.mvi.observe import net.thunderbird.core.ui.navigation.deepLinkComposable -import net.thunderbird.feature.thundermail.internal.common.R -import net.thunderbird.feature.thundermail.internal.common.ui.ThundermailContract +import net.thunderbird.feature.thundermail.internal.common.ui.ThundermailOAuthRedirectScreen import net.thunderbird.feature.thundermail.navigation.ThundermailNavigation import net.thunderbird.feature.thundermail.navigation.ThundermailRoute import net.thunderbird.feature.thundermail.navigation.ThundermailRoute.Companion.ACCOUNT_ID_ROUTE_PARAM -import org.koin.androidx.compose.koinViewModel class DefaultThundermailNavigation : ThundermailNavigation { override fun registerRoutes( @@ -45,7 +22,7 @@ class DefaultThundermailNavigation : ThundermailNavigation { deepLinkComposable( basePath = ThundermailRoute.SIGN_IN_WITH_THUNDERMAIL_ROUTE, ) { - ThundermailOAuthRedirectScreen(onFinish = onFinish) + ThundermailOAuthRedirectScreen(onFinish = onFinish, onBack = onBack) } deepLinkComposable( @@ -94,49 +71,3 @@ class DefaultThundermailNavigation : ThundermailNavigation { } } } - -@Composable -private fun ThundermailOAuthRedirectScreen( - viewModel: ThundermailContract.ViewModel = koinViewModel(), - onFinish: (ThundermailRoute) -> Unit, -) { - val oAuthLauncher = rememberLauncherForActivityResult( - contract = ActivityResultContracts.StartActivityForResult(), - ) { - viewModel.event(ThundermailContract.Event.OnOAuthResult(it.resultCode, it.data)) - } - - val (state, dispatch) = viewModel.observe { effect -> - when (effect) { - is ThundermailContract.Effect.LaunchOAuth -> oAuthLauncher.launch(effect.intent) - is ThundermailContract.Effect.NavigateToIncomingServerSettings -> - onFinish(ThundermailRoute.IncomingSettings) - } - } - - var launchedOAuth by remember { mutableStateOf(false) } - - LaunchedEffect(state.value.initialized) { - if (state.value.initialized && !launchedOAuth) { - dispatch(ThundermailContract.Event.SignInClicked) - launchedOAuth = true - } - } - - Box( - modifier = Modifier - .fillMaxSize() - .safeContentPadding(), - ) { - Column( - modifier = Modifier.align(Alignment.Center), - verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.double), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - CircularProgressIndicator( - modifier = Modifier.size(MainTheme.sizes.medium), - ) - TextBodyLarge(stringResource(R.string.thundermail_redirecting)) - } - } -} diff --git a/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/ui/ThundermailContract.kt b/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/ui/ThundermailContract.kt index 6cdb8823937..16bc376be38 100644 --- a/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/ui/ThundermailContract.kt +++ b/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/ui/ThundermailContract.kt @@ -1,11 +1,16 @@ package net.thunderbird.feature.thundermail.internal.common.ui import android.content.Intent +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable import com.fsck.k9.mail.ServerSettings import net.thunderbird.core.ui.contract.mvi.BaseViewModel interface ThundermailContract { + @Stable abstract class ViewModel(initialState: State) : BaseViewModel(initialState) + + @Immutable data class State( val initialized: Boolean = false, val incomingServerSettings: ServerSettings? = null, @@ -26,6 +31,7 @@ interface ThundermailContract { data class OnOAuthResult(val resultCode: Int, val data: Intent?) : Event } + @Immutable sealed interface Error { data object Canceled : Error data object BrowserNotAvailable : Error diff --git a/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/ui/ThundermailOAuthRedirectScreen.kt b/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/ui/ThundermailOAuthRedirectScreen.kt new file mode 100644 index 00000000000..6b306cb3343 --- /dev/null +++ b/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/ui/ThundermailOAuthRedirectScreen.kt @@ -0,0 +1,155 @@ +package net.thunderbird.feature.thundermail.internal.common.ui + +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.animation.Crossfade +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeContentPadding +import androidx.compose.foundation.layout.size +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.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import app.k9mail.core.ui.compose.designsystem.atom.CircularProgressIndicator +import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyLarge +import app.k9mail.core.ui.compose.designsystem.atom.text.TextTitleLarge +import app.k9mail.core.ui.compose.designsystem.template.Scaffold +import app.k9mail.feature.account.common.ui.WizardNavigationBar +import app.k9mail.feature.account.common.ui.WizardNavigationBarState +import net.thunderbird.core.ui.compose.theme2.MainTheme +import net.thunderbird.core.ui.contract.mvi.observe +import net.thunderbird.feature.thundermail.internal.common.R +import net.thunderbird.feature.thundermail.navigation.ThundermailRoute +import org.koin.androidx.compose.koinViewModel + +@Composable +internal fun ThundermailOAuthRedirectScreen( + onBack: () -> Unit, + onFinish: (ThundermailRoute) -> Unit, + modifier: Modifier = Modifier, + viewModel: ThundermailContract.ViewModel = koinViewModel(), +) { + val oAuthLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartActivityForResult(), + ) { + viewModel.event(ThundermailContract.Event.OnOAuthResult(it.resultCode, it.data)) + } + + val (state, dispatch) = viewModel.observe { effect -> + when (effect) { + is ThundermailContract.Effect.LaunchOAuth -> oAuthLauncher.launch(effect.intent) + is ThundermailContract.Effect.NavigateToIncomingServerSettings -> + onFinish(ThundermailRoute.IncomingSettings) + } + } + + var launchedOAuth by remember { mutableStateOf(false) } + + LaunchedEffect(state.value.initialized) { + if (state.value.initialized && !launchedOAuth) { + dispatch(ThundermailContract.Event.SignInClicked) + launchedOAuth = true + } + } + + LaunchedErrorEffect(state.value, onBack) + + ThundermailOAuthRedirectScreen(state = state.value, onBack = onBack, modifier = modifier) +} + +@Composable +internal fun ThundermailOAuthRedirectScreen( + state: ThundermailContract.State, + onBack: () -> Unit, + modifier: Modifier = Modifier, +) { + Scaffold( + bottomBar = { + WizardNavigationBar( + onNextClick = {}, + onBackClick = onBack, + state = WizardNavigationBarState(showNext = false, showBack = state.error != null), + ) + }, + modifier = modifier, + ) { paddingValues -> + Box( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + .safeContentPadding() + .padding(horizontal = MainTheme.spacings.quadruple), + ) { + Crossfade( + targetState = state.error, + modifier = Modifier.align(Alignment.Center), + ) { error -> + if (error == null) { + RedirectingUserContent(modifier = Modifier.fillMaxWidth()) + } else { + ErrorDetails(error, modifier = Modifier.fillMaxWidth()) + } + } + } + } +} + +@Composable +private fun LaunchedErrorEffect( + state: ThundermailContract.State, + onBack: () -> Unit, +) { + LaunchedEffect(state.error) { + when (state.error) { + ThundermailContract.Error.Canceled -> onBack() + else -> Unit + } + } +} + +@Composable +private fun RedirectingUserContent(modifier: Modifier = Modifier) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.double), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { + CircularProgressIndicator( + modifier = Modifier.size(MainTheme.sizes.medium), + ) + } + TextBodyLarge(stringResource(R.string.thundermail_redirecting)) + } +} + +@Composable +private fun ErrorDetails(error: ThundermailContract.Error, modifier: Modifier = Modifier) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.double), + ) { + TextTitleLarge(text = stringResource(R.string.thundermail_something_went_wrong)) + TextBodyLarge( + text = when (error) { + ThundermailContract.Error.BrowserNotAvailable -> + stringResource(R.string.thundermail_browser_is_not_available) + + ThundermailContract.Error.Canceled -> stringResource(R.string.thundermail_operation_was_canceled) + + is ThundermailContract.Error.Unknown -> + stringResource(R.string.thundermail_unknown_error_please_consider_reporting) + }, + ) + } +} diff --git a/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/ui/ThundermailOAuthViewModel.kt b/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/ui/ThundermailOAuthViewModel.kt index 35dd25ba253..b3cf461d54e 100644 --- a/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/ui/ThundermailOAuthViewModel.kt +++ b/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/ui/ThundermailOAuthViewModel.kt @@ -6,9 +6,10 @@ import app.k9mail.feature.account.oauth.ui.AccountOAuthContract import app.k9mail.feature.account.oauth.ui.AccountOAuthViewModel import app.k9mail.feature.account.setup.domain.DomainContract.UseCase.GetAutoDiscovery import app.k9mail.feature.account.setup.domain.toServerSettings +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flatMapConcat import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import net.thunderbird.core.logging.Logger import net.thunderbird.core.outcome.handle @@ -18,6 +19,7 @@ private const val TAG = "ThundermailOAuthViewModel" private const val OAUTH_AUTO_DISCOVERY = "oauth-autodiscovery@thundermail.com" +@OptIn(ExperimentalCoroutinesApi::class) internal class ThundermailOAuthViewModel( private val logger: Logger, private val accountOAuthViewModel: AccountOAuthViewModel, @@ -27,7 +29,7 @@ internal class ThundermailOAuthViewModel( init { flow { emit(getAutoDiscovery.execute(OAUTH_AUTO_DISCOVERY)) } - .map { result -> + .flatMapConcat { result -> when (result) { is AutoDiscoveryResult.Settings -> { val incomingServerSettings = result.incomingServerSettings.toServerSettings(password = null) @@ -50,8 +52,19 @@ internal class ThundermailOAuthViewModel( accountOAuthViewModel.state } .onEach { state -> - updateState { it.copy(initialized = true) } - logger.verbose(TAG) { "accountOAuthViewModel.state() called with: state = $state" } + updateState { + it.copy( + initialized = true, + error = when (val error = state.error) { + AccountOAuthContract.Error.BrowserNotAvailable -> + ThundermailContract.Error.BrowserNotAvailable + + AccountOAuthContract.Error.Canceled -> ThundermailContract.Error.Canceled + is AccountOAuthContract.Error.Unknown -> ThundermailContract.Error.Unknown(error.error) + else -> null + }, + ) + } } .launchIn(viewModelScope) diff --git a/feature/thundermail/internal/common/src/main/res/values/strings.xml b/feature/thundermail/internal/common/src/main/res/values/strings.xml index b1c52efcb3a..4d1aecc6c5f 100644 --- a/feature/thundermail/internal/common/src/main/res/values/strings.xml +++ b/feature/thundermail/internal/common/src/main/res/values/strings.xml @@ -1,4 +1,8 @@ Redirecting to Thundermail... + Something went wrong + Browser is not available. Please verify your\'s device configuration + Operation was canceled + Unknown error. Please consider reporting.