Secure backup
This commit is contained in:
parent
bf905dd79b
commit
9807ebf649
115 changed files with 4698 additions and 393 deletions
|
|
@ -30,6 +30,7 @@ import dagger.assisted.Assisted
|
|||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
|
||||
import io.element.android.features.logout.api.LogoutEntryPoint
|
||||
import io.element.android.features.preferences.api.PreferencesEntryPoint
|
||||
import io.element.android.features.preferences.impl.about.AboutNode
|
||||
import io.element.android.features.preferences.impl.advanced.AdvancedSettingsNode
|
||||
|
|
@ -53,6 +54,7 @@ class PreferencesFlowNode @AssistedInject constructor(
|
|||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val lockScreenEntryPoint: LockScreenEntryPoint,
|
||||
private val logoutEntryPoint: LogoutEntryPoint,
|
||||
) : BackstackNode<PreferencesFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
initialElement = plugins.filterIsInstance<PreferencesEntryPoint.Params>().first().initialElement.toNavTarget(),
|
||||
|
|
@ -92,6 +94,9 @@ class PreferencesFlowNode @AssistedInject constructor(
|
|||
|
||||
@Parcelize
|
||||
data class UserProfile(val matrixUser: MatrixUser) : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object SignOut : NavTarget
|
||||
}
|
||||
|
||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||
|
|
@ -106,6 +111,10 @@ class PreferencesFlowNode @AssistedInject constructor(
|
|||
plugins<PreferencesEntryPoint.Callback>().forEach { it.onVerifyClicked() }
|
||||
}
|
||||
|
||||
override fun onSecureBackupClicked() {
|
||||
plugins<PreferencesEntryPoint.Callback>().forEach { it.onSecureBackupClicked() }
|
||||
}
|
||||
|
||||
override fun onOpenAnalytics() {
|
||||
backstack.push(NavTarget.AnalyticsSettings)
|
||||
}
|
||||
|
|
@ -133,6 +142,10 @@ class PreferencesFlowNode @AssistedInject constructor(
|
|||
override fun onOpenUserProfile(matrixUser: MatrixUser) {
|
||||
backstack.push(NavTarget.UserProfile(matrixUser))
|
||||
}
|
||||
|
||||
override fun onSignOutClicked() {
|
||||
backstack.push(NavTarget.SignOut)
|
||||
}
|
||||
}
|
||||
createNode<PreferencesRootNode>(buildContext, plugins = listOf(callback))
|
||||
}
|
||||
|
|
@ -182,6 +195,16 @@ class PreferencesFlowNode @AssistedInject constructor(
|
|||
.target(LockScreenEntryPoint.Target.Settings)
|
||||
.build()
|
||||
}
|
||||
NavTarget.SignOut -> {
|
||||
val callBack: LogoutEntryPoint.Callback = object : LogoutEntryPoint.Callback {
|
||||
override fun onChangeRecoveryKeyClicked() {
|
||||
plugins<PreferencesEntryPoint.Callback>().forEach { it.onSecureBackupClicked() }
|
||||
}
|
||||
}
|
||||
logoutEntryPoint.nodeBuilder(this, buildContext)
|
||||
.callback(callBack)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTa
|
|||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.theme.ElementTheme
|
||||
import timber.log.Timber
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
class PreferencesRootNode @AssistedInject constructor(
|
||||
|
|
@ -43,6 +42,7 @@ class PreferencesRootNode @AssistedInject constructor(
|
|||
interface Callback : Plugin {
|
||||
fun onOpenBugReport()
|
||||
fun onVerifyClicked()
|
||||
fun onSecureBackupClicked()
|
||||
fun onOpenAnalytics()
|
||||
fun onOpenAbout()
|
||||
fun onOpenDeveloperSettings()
|
||||
|
|
@ -50,6 +50,7 @@ class PreferencesRootNode @AssistedInject constructor(
|
|||
fun onOpenLockScreenSettings()
|
||||
fun onOpenAdvancedSettings()
|
||||
fun onOpenUserProfile(matrixUser: MatrixUser)
|
||||
fun onSignOutClicked()
|
||||
}
|
||||
|
||||
private fun onOpenBugReport() {
|
||||
|
|
@ -60,6 +61,10 @@ class PreferencesRootNode @AssistedInject constructor(
|
|||
plugins<Callback>().forEach { it.onVerifyClicked() }
|
||||
}
|
||||
|
||||
private fun onSecureBackupClicked() {
|
||||
plugins<Callback>().forEach { it.onSecureBackupClicked() }
|
||||
}
|
||||
|
||||
private fun onOpenDeveloperSettings() {
|
||||
plugins<Callback>().forEach { it.onOpenDeveloperSettings() }
|
||||
}
|
||||
|
|
@ -102,6 +107,10 @@ class PreferencesRootNode @AssistedInject constructor(
|
|||
plugins<Callback>().forEach { it.onOpenUserProfile(matrixUser) }
|
||||
}
|
||||
|
||||
private fun onSignOutClicked() {
|
||||
plugins<Callback>().forEach { it.onSignOutClicked() }
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
|
|
@ -115,20 +124,14 @@ class PreferencesRootNode @AssistedInject constructor(
|
|||
onOpenAnalytics = this::onOpenAnalytics,
|
||||
onOpenAbout = this::onOpenAbout,
|
||||
onVerifyClicked = this::onVerifyClicked,
|
||||
onSecureBackupClicked = this::onSecureBackupClicked,
|
||||
onOpenDeveloperSettings = this::onOpenDeveloperSettings,
|
||||
onOpenAdvancedSettings = this::onOpenAdvancedSettings,
|
||||
onSuccessLogout = { onSuccessLogout(activity, it) },
|
||||
onManageAccountClicked = { onManageAccountClicked(activity, it, isDark) },
|
||||
onOpenNotificationSettings = this::onOpenNotificationSettings,
|
||||
onOpenLockScreenSettings = this::onOpenLockScreenSettings,
|
||||
onOpenUserProfile = this::onOpenUserProfile,
|
||||
onSignOutClicked = this::onSignOutClicked,
|
||||
)
|
||||
}
|
||||
|
||||
private fun onSuccessLogout(activity: Activity, url: String?) {
|
||||
Timber.d("Success logout with result url: $url")
|
||||
url?.let {
|
||||
activity.openUrlInChromeCustomTab(null, false, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,13 +24,13 @@ 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.LogoutPreferencePresenter
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.meta.BuildType
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.indicator.api.IndicatorService
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
|
|
@ -42,7 +42,6 @@ import kotlinx.coroutines.launch
|
|||
import javax.inject.Inject
|
||||
|
||||
class PreferencesRootPresenter @Inject constructor(
|
||||
private val logoutPresenter: LogoutPreferencePresenter,
|
||||
private val matrixClient: MatrixClient,
|
||||
private val sessionVerificationService: SessionVerificationService,
|
||||
private val analyticsService: AnalyticsService,
|
||||
|
|
@ -50,6 +49,7 @@ class PreferencesRootPresenter @Inject constructor(
|
|||
private val versionFormatter: VersionFormatter,
|
||||
private val snackbarDispatcher: SnackbarDispatcher,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val indicatorService: IndicatorService,
|
||||
) : Presenter<PreferencesRootState> {
|
||||
|
||||
@Composable
|
||||
|
|
@ -76,6 +76,8 @@ class PreferencesRootPresenter @Inject constructor(
|
|||
// We should display the 'complete verification' option if the current session can be verified
|
||||
val showCompleteVerification by sessionVerificationService.canVerifySessionFlow.collectAsState(false)
|
||||
|
||||
val showSecureBackupIndicator by indicatorService.showSettingChatBackupIndicator()
|
||||
|
||||
val accountManagementUrl: MutableState<String?> = remember {
|
||||
mutableStateOf(null)
|
||||
}
|
||||
|
|
@ -87,13 +89,13 @@ class PreferencesRootPresenter @Inject constructor(
|
|||
initAccountManagementUrl(accountManagementUrl, devicesManagementUrl)
|
||||
}
|
||||
|
||||
val logoutState = logoutPresenter.present()
|
||||
val showDeveloperSettings = buildType != BuildType.RELEASE
|
||||
return PreferencesRootState(
|
||||
logoutState = logoutState,
|
||||
myUser = matrixUser.value,
|
||||
version = versionFormatter.get(),
|
||||
showCompleteVerification = showCompleteVerification,
|
||||
showSecureBackup = !showCompleteVerification,
|
||||
showSecureBackupBadge = showSecureBackupIndicator,
|
||||
accountManagementUrl = accountManagementUrl.value,
|
||||
devicesManagementUrl = devicesManagementUrl.value,
|
||||
showAnalyticsSettings = hasAnalyticsProviders,
|
||||
|
|
|
|||
|
|
@ -16,15 +16,15 @@
|
|||
|
||||
package io.element.android.features.preferences.impl.root
|
||||
|
||||
import io.element.android.features.logout.api.LogoutPreferenceState
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
|
||||
data class PreferencesRootState(
|
||||
val logoutState: LogoutPreferenceState,
|
||||
val myUser: MatrixUser?,
|
||||
val version: String,
|
||||
val showCompleteVerification: Boolean,
|
||||
val showSecureBackup: Boolean,
|
||||
val showSecureBackupBadge: Boolean,
|
||||
val accountManagementUrl: String?,
|
||||
val devicesManagementUrl: String?,
|
||||
val showAnalyticsSettings: Boolean,
|
||||
|
|
|
|||
|
|
@ -16,15 +16,15 @@
|
|||
|
||||
package io.element.android.features.preferences.impl.root
|
||||
|
||||
import io.element.android.features.logout.api.aLogoutPreferenceState
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
fun aPreferencesRootState() = PreferencesRootState(
|
||||
logoutState = aLogoutPreferenceState(),
|
||||
myUser = null,
|
||||
version = "Version 1.1 (1)",
|
||||
showCompleteVerification = true,
|
||||
showSecureBackup = true,
|
||||
showSecureBackupBadge = true,
|
||||
accountManagementUrl = "aUrl",
|
||||
devicesManagementUrl = "anOtherUrl",
|
||||
showAnalyticsSettings = true,
|
||||
|
|
|
|||
|
|
@ -29,10 +29,9 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.features.logout.api.LogoutPreferenceView
|
||||
import io.element.android.features.preferences.impl.user.UserPreferences
|
||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceText
|
||||
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceText
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||
import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight
|
||||
|
|
@ -51,6 +50,7 @@ fun PreferencesRootView(
|
|||
state: PreferencesRootState,
|
||||
onBackPressed: () -> Unit,
|
||||
onVerifyClicked: () -> Unit,
|
||||
onSecureBackupClicked: () -> Unit,
|
||||
onManageAccountClicked: (url: String) -> Unit,
|
||||
onOpenAnalytics: () -> Unit,
|
||||
onOpenRageShake: () -> Unit,
|
||||
|
|
@ -58,9 +58,9 @@ fun PreferencesRootView(
|
|||
onOpenAbout: () -> Unit,
|
||||
onOpenDeveloperSettings: () -> Unit,
|
||||
onOpenAdvancedSettings: () -> Unit,
|
||||
onSuccessLogout: (logoutUrlResult: String?) -> Unit,
|
||||
onOpenNotificationSettings: () -> Unit,
|
||||
onOpenUserProfile: (MatrixUser) -> Unit,
|
||||
onSignOutClicked: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage)
|
||||
|
|
@ -84,6 +84,16 @@ fun PreferencesRootView(
|
|||
icon = Icons.Outlined.VerifiedUser,
|
||||
onClick = onVerifyClicked,
|
||||
)
|
||||
}
|
||||
if (state.showSecureBackup) {
|
||||
PreferenceText(
|
||||
title = stringResource(id = CommonStrings.common_chat_backup),
|
||||
iconResourceId = CommonDrawables.ic_key_filled,
|
||||
showEndBadge = state.showSecureBackupBadge,
|
||||
onClick = onSecureBackupClicked,
|
||||
)
|
||||
}
|
||||
if (state.showCompleteVerification || state.showSecureBackup) {
|
||||
HorizontalDivider()
|
||||
}
|
||||
if (state.accountManagementUrl != null) {
|
||||
|
|
@ -143,9 +153,10 @@ fun PreferencesRootView(
|
|||
DeveloperPreferencesView(onOpenDeveloperSettings)
|
||||
}
|
||||
HorizontalDivider()
|
||||
LogoutPreferenceView(
|
||||
state = state.logoutState,
|
||||
onSuccessLogout = onSuccessLogout,
|
||||
PreferenceText(
|
||||
title = stringResource(id = CommonStrings.action_signout),
|
||||
iconResourceId = CommonDrawables.ic_compound_leave,
|
||||
onClick = onSignOutClicked,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
|
|
@ -189,10 +200,11 @@ private fun ContentToPreview(matrixUser: MatrixUser) {
|
|||
onOpenAdvancedSettings = {},
|
||||
onOpenAbout = {},
|
||||
onVerifyClicked = {},
|
||||
onSuccessLogout = {},
|
||||
onSecureBackupClicked = {},
|
||||
onManageAccountClicked = {},
|
||||
onOpenNotificationSettings = {},
|
||||
onOpenLockScreenSettings = {},
|
||||
onOpenUserProfile = {},
|
||||
onSignOutClicked = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,15 +20,15 @@ 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.impl.DefaultLogoutPreferencePresenter
|
||||
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
|
||||
import io.element.android.libraries.indicator.impl.DefaultIndicatorService
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
||||
import io.element.android.libraries.matrix.test.A_USER_NAME
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
|
||||
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
|
|
@ -44,16 +44,19 @@ class PreferencesRootPresenterTest {
|
|||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val matrixClient = FakeMatrixClient()
|
||||
val logoutPresenter = DefaultLogoutPreferencePresenter(matrixClient)
|
||||
val sessionVerificationService = FakeSessionVerificationService()
|
||||
val presenter = PreferencesRootPresenter(
|
||||
logoutPresenter,
|
||||
matrixClient,
|
||||
FakeSessionVerificationService(),
|
||||
sessionVerificationService,
|
||||
FakeAnalyticsService(),
|
||||
BuildType.DEBUG,
|
||||
FakeVersionFormatter(),
|
||||
SnackbarDispatcher(),
|
||||
FakeFeatureFlagService()
|
||||
FakeFeatureFlagService(),
|
||||
DefaultIndicatorService(
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
encryptionService = FakeEncryptionService(),
|
||||
),
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -62,7 +65,6 @@ class PreferencesRootPresenterTest {
|
|||
assertThat(initialState.myUser).isNull()
|
||||
assertThat(initialState.version).isEqualTo("A Version")
|
||||
val loadedState = awaitItem()
|
||||
assertThat(loadedState.logoutState.logoutAction).isEqualTo(Async.Uninitialized)
|
||||
assertThat(loadedState.myUser).isEqualTo(
|
||||
MatrixUser(
|
||||
userId = matrixClient.sessionId,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue