Sign out: direct flow if not last session and if not currently backing up keys #2072

Extract some stuff from existing Logout to avoid duplication.
This commit is contained in:
Benoit Marty 2023-12-26 12:10:11 +01:00
parent c6f5c0a556
commit 2b983e923e
16 changed files with 477 additions and 52 deletions

View file

@ -27,16 +27,19 @@ import com.bumble.appyx.core.plugin.plugins
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.compound.theme.ElementTheme
import io.element.android.features.logout.api.direct.DirectLogoutView
import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.compound.theme.ElementTheme
import timber.log.Timber
@ContributesNode(SessionScope::class)
class PreferencesRootNode @AssistedInject constructor(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
private val presenter: PreferencesRootPresenter,
private val directLogoutView: DirectLogoutView,
) : Node(buildContext, plugins = plugins) {
interface Callback : Plugin {
@ -95,6 +98,13 @@ class PreferencesRootNode @AssistedInject constructor(
}
}
private fun onSuccessLogout(activity: Activity, url: String?) {
Timber.d("Success (direct) logout with result url: $url")
url?.let {
activity.openUrlInChromeCustomTab(null, false, it)
}
}
private fun onOpenNotificationSettings() {
plugins<Callback>().forEach { it.onOpenNotificationSettings() }
}
@ -133,5 +143,12 @@ class PreferencesRootNode @AssistedInject constructor(
onOpenUserProfile = this::onOpenUserProfile,
onSignOutClicked = this::onSignOutClicked,
)
directLogoutView.render(
state = state.directLogoutState,
onSuccessLogout = {
onSuccessLogout(activity, it)
}
)
}
}

View file

@ -24,6 +24,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import io.element.android.features.logout.api.direct.DirectLogoutPresenter
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.meta.BuildType
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
@ -50,6 +51,7 @@ class PreferencesRootPresenter @Inject constructor(
private val snackbarDispatcher: SnackbarDispatcher,
private val featureFlagService: FeatureFlagService,
private val indicatorService: IndicatorService,
private val directLogoutPresenter: DirectLogoutPresenter,
) : Presenter<PreferencesRootState> {
@Composable
@ -88,6 +90,8 @@ class PreferencesRootPresenter @Inject constructor(
mutableStateOf(null)
}
val directLogoutState = directLogoutPresenter.present()
LaunchedEffect(Unit) {
initAccountManagementUrl(accountManagementUrl, devicesManagementUrl)
}
@ -105,6 +109,7 @@ class PreferencesRootPresenter @Inject constructor(
showDeveloperSettings = showDeveloperSettings,
showNotificationSettings = showNotificationSettings.value,
showLockScreenSettings = showLockScreenSettings.value,
directLogoutState = directLogoutState,
snackbarMessage = snackbarMessage,
)
}

View file

@ -16,6 +16,7 @@
package io.element.android.features.preferences.impl.root
import io.element.android.features.logout.api.direct.DirectLogoutState
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
import io.element.android.libraries.matrix.api.user.MatrixUser
@ -31,5 +32,6 @@ data class PreferencesRootState(
val showDeveloperSettings: Boolean,
val showLockScreenSettings: Boolean,
val showNotificationSettings: Boolean,
val directLogoutState: DirectLogoutState,
val snackbarMessage: SnackbarMessage?,
)

View file

@ -16,6 +16,8 @@
package io.element.android.features.preferences.impl.root
import io.element.android.features.logout.api.direct.DirectLogoutState
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
import io.element.android.libraries.ui.strings.CommonStrings
@ -32,4 +34,12 @@ fun aPreferencesRootState() = PreferencesRootState(
showNotificationSettings = true,
showLockScreenSettings = true,
snackbarMessage = SnackbarMessage(CommonStrings.common_verification_complete),
directLogoutState = aDirectLogoutState(),
)
fun aDirectLogoutState() = DirectLogoutState(
canDoDirectSignOut = true,
showConfirmationDialog = false,
logoutAction = Async.Uninitialized,
eventSink = {},
)

View file

@ -29,6 +29,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.logout.api.direct.DirectLogoutEvents
import io.element.android.features.preferences.impl.R
import io.element.android.features.preferences.impl.user.UserPreferences
import io.element.android.libraries.designsystem.components.list.ListItemContent
@ -57,7 +58,7 @@ fun PreferencesRootView(
onManageAccountClicked: (url: String) -> Unit,
onOpenAnalytics: () -> Unit,
onOpenRageShake: () -> Unit,
onOpenLockScreenSettings: ()->Unit,
onOpenLockScreenSettings: () -> Unit,
onOpenAbout: () -> Unit,
onOpenDeveloperSettings: () -> Unit,
onOpenAdvancedSettings: () -> Unit,
@ -91,7 +92,7 @@ fun PreferencesRootView(
if (state.showSecureBackup) {
ListItem(
headlineContent = { Text(stringResource(id = CommonStrings.common_chat_backup)) },
leadingContent = ListItemContent.Icon(IconSource.Resource(CommonDrawables.ic_key_filled),),
leadingContent = ListItemContent.Icon(IconSource.Resource(CommonDrawables.ic_key_filled)),
trailingContent = ListItemContent.Badge.takeIf { state.showSecureBackupBadge },
onClick = onSecureBackupClicked,
)
@ -162,7 +163,13 @@ fun PreferencesRootView(
headlineContent = { Text(stringResource(id = CommonStrings.action_signout)) },
leadingContent = ListItemContent.Icon(IconSource.Resource(CommonDrawables.ic_sign_out)),
style = ListItemStyle.Destructive,
onClick = onSignOutClicked,
onClick = {
if (state.directLogoutState.canDoDirectSignOut) {
state.directLogoutState.eventSink(DirectLogoutEvents.Logout(ignoreSdkError = false))
} else {
onSignOutClicked()
}
},
)
Text(
modifier = Modifier

View file

@ -16,10 +16,14 @@
package io.element.android.features.preferences.impl.root
import androidx.compose.runtime.Composable
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.features.logout.api.direct.DirectLogoutPresenter
import io.element.android.features.logout.api.direct.DirectLogoutState
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.core.meta.BuildType
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
@ -41,23 +45,34 @@ class PreferencesRootPresenterTest {
@get:Rule
val warmUpRule = WarmUpRule()
private val aDirectLogoutState = DirectLogoutState(
canDoDirectSignOut = true,
showConfirmationDialog = false,
logoutAction = Async.Uninitialized,
eventSink = {},
)
@Test
fun `present - initial state`() = runTest {
val matrixClient = FakeMatrixClient()
val sessionVerificationService = FakeSessionVerificationService()
val presenter = PreferencesRootPresenter(
matrixClient,
sessionVerificationService,
FakeAnalyticsService(),
BuildType.DEBUG,
FakeVersionFormatter(),
SnackbarDispatcher(),
FakeFeatureFlagService(),
DefaultIndicatorService(
matrixClient = matrixClient,
sessionVerificationService = sessionVerificationService,
analyticsService = FakeAnalyticsService(),
buildType = BuildType.DEBUG,
versionFormatter = FakeVersionFormatter(),
snackbarDispatcher = SnackbarDispatcher(),
featureFlagService = FakeFeatureFlagService(),
indicatorService = DefaultIndicatorService(
sessionVerificationService = sessionVerificationService,
encryptionService = FakeEncryptionService(),
featureFlagService = FakeFeatureFlagService(),
),
directLogoutPresenter = object : DirectLogoutPresenter {
@Composable
override fun present() = aDirectLogoutState
}
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -77,6 +92,7 @@ class PreferencesRootPresenterTest {
assertThat(loadedState.showAnalyticsSettings).isFalse()
assertThat(loadedState.accountManagementUrl).isNull()
assertThat(loadedState.devicesManagementUrl).isNull()
assertThat(loadedState.directLogoutState).isEqualTo(aDirectLogoutState)
}
}
}