|
|
@ -16,10 +16,9 @@ |
|
|
|
|
|
|
|
|
|
|
|
package io.element.android.features.securebackup.impl.setup |
|
|
|
package io.element.android.features.securebackup.impl.setup |
|
|
|
|
|
|
|
|
|
|
|
import androidx.activity.compose.BackHandler |
|
|
|
import androidx.compose.foundation.layout.ColumnScope |
|
|
|
import androidx.compose.foundation.layout.fillMaxWidth |
|
|
|
import androidx.compose.foundation.layout.fillMaxWidth |
|
|
|
import androidx.compose.foundation.layout.padding |
|
|
|
import androidx.compose.foundation.layout.padding |
|
|
|
import androidx.compose.material3.ExperimentalMaterial3Api |
|
|
|
|
|
|
|
import androidx.compose.runtime.Composable |
|
|
|
import androidx.compose.runtime.Composable |
|
|
|
import androidx.compose.ui.Modifier |
|
|
|
import androidx.compose.ui.Modifier |
|
|
|
import androidx.compose.ui.platform.LocalContext |
|
|
|
import androidx.compose.ui.platform.LocalContext |
|
|
@ -28,24 +27,18 @@ import androidx.compose.ui.tooling.preview.PreviewParameter |
|
|
|
import androidx.compose.ui.unit.dp |
|
|
|
import androidx.compose.ui.unit.dp |
|
|
|
import io.element.android.features.securebackup.impl.R |
|
|
|
import io.element.android.features.securebackup.impl.R |
|
|
|
import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyView |
|
|
|
import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyView |
|
|
|
import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyViewState |
|
|
|
|
|
|
|
import io.element.android.libraries.androidutils.system.copyToClipboard |
|
|
|
import io.element.android.libraries.androidutils.system.copyToClipboard |
|
|
|
import io.element.android.libraries.androidutils.system.startSharePlainTextIntent |
|
|
|
import io.element.android.libraries.androidutils.system.startSharePlainTextIntent |
|
|
|
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule |
|
|
|
import io.element.android.libraries.designsystem.atomic.pages.UserStoryFlowPage |
|
|
|
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule |
|
|
|
|
|
|
|
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage |
|
|
|
|
|
|
|
import io.element.android.libraries.designsystem.components.button.BackButton |
|
|
|
|
|
|
|
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog |
|
|
|
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog |
|
|
|
import io.element.android.libraries.designsystem.preview.ElementPreview |
|
|
|
import io.element.android.libraries.designsystem.preview.ElementPreview |
|
|
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight |
|
|
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight |
|
|
|
import io.element.android.libraries.designsystem.theme.components.Button |
|
|
|
import io.element.android.libraries.designsystem.theme.components.Button |
|
|
|
import io.element.android.libraries.designsystem.theme.components.IconSource |
|
|
|
import io.element.android.libraries.designsystem.theme.components.IconSource |
|
|
|
import io.element.android.libraries.designsystem.theme.components.OutlinedButton |
|
|
|
import io.element.android.libraries.designsystem.theme.components.OutlinedButton |
|
|
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar |
|
|
|
|
|
|
|
import io.element.android.libraries.designsystem.utils.CommonDrawables |
|
|
|
import io.element.android.libraries.designsystem.utils.CommonDrawables |
|
|
|
import io.element.android.libraries.ui.strings.CommonStrings |
|
|
|
import io.element.android.libraries.ui.strings.CommonStrings |
|
|
|
|
|
|
|
|
|
|
|
@OptIn(ExperimentalMaterial3Api::class) |
|
|
|
|
|
|
|
@Composable |
|
|
|
@Composable |
|
|
|
fun SecureBackupSetupView( |
|
|
|
fun SecureBackupSetupView( |
|
|
|
state: SecureBackupSetupState, |
|
|
|
state: SecureBackupSetupState, |
|
|
@ -53,68 +46,16 @@ fun SecureBackupSetupView( |
|
|
|
onBackClicked: () -> Unit, |
|
|
|
onBackClicked: () -> Unit, |
|
|
|
modifier: Modifier = Modifier, |
|
|
|
modifier: Modifier = Modifier, |
|
|
|
) { |
|
|
|
) { |
|
|
|
val context = LocalContext.current |
|
|
|
UserStoryFlowPage( |
|
|
|
val canGoBack = state.canGoBack() |
|
|
|
|
|
|
|
BackHandler(enabled = canGoBack) { |
|
|
|
|
|
|
|
onBackClicked() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
HeaderFooterPage( |
|
|
|
|
|
|
|
modifier = modifier, |
|
|
|
modifier = modifier, |
|
|
|
topBar = { |
|
|
|
canGoBack = state.canGoBack(), |
|
|
|
TopAppBar( |
|
|
|
onBackClicked = onBackClicked, |
|
|
|
navigationIcon = { |
|
|
|
title = title(state), |
|
|
|
if (canGoBack) { |
|
|
|
subTitle = subtitle(state), |
|
|
|
BackButton(onClick = onBackClicked) |
|
|
|
iconResourceId = CommonDrawables.ic_key, |
|
|
|
} |
|
|
|
content = { Content(state) }, |
|
|
|
}, |
|
|
|
buttons = { Buttons(state, onDone = onDone) }, |
|
|
|
title = {}, |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
header = { |
|
|
|
|
|
|
|
HeaderContent(state = state) |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
footer = { |
|
|
|
|
|
|
|
val chooserTitle = stringResource(id = R.string.screen_recovery_key_save_action) |
|
|
|
|
|
|
|
BottomMenu( |
|
|
|
|
|
|
|
state = state, |
|
|
|
|
|
|
|
onSaveClicked = { key -> |
|
|
|
|
|
|
|
context.startSharePlainTextIntent( |
|
|
|
|
|
|
|
activityResultLauncher = null, |
|
|
|
|
|
|
|
chooserTitle = chooserTitle, |
|
|
|
|
|
|
|
text = key, |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
state.eventSink.invoke(SecureBackupSetupEvents.RecoveryKeyHasBeenSaved) |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
onDone = { |
|
|
|
|
|
|
|
if (state.setupState is SetupState.CreatedAndSaved) { |
|
|
|
|
|
|
|
onDone() |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
state.eventSink.invoke(SecureBackupSetupEvents.Done) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
) { |
|
|
|
|
|
|
|
val formattedRecoveryKey = state.recoveryKeyViewState.formattedRecoveryKey |
|
|
|
|
|
|
|
val clickLambda = if (formattedRecoveryKey != null) { |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
context.copyToClipboard( |
|
|
|
|
|
|
|
formattedRecoveryKey, |
|
|
|
|
|
|
|
context.getString(R.string.screen_recovery_key_copied_to_clipboard) |
|
|
|
|
|
|
|
) |
|
|
|
) |
|
|
|
state.eventSink.invoke(SecureBackupSetupEvents.RecoveryKeyHasBeenSaved) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
if (!state.recoveryKeyViewState.inProgress) { |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
state.eventSink.invoke(SecureBackupSetupEvents.CreateRecoveryKey) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
null |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
Content(state = state.recoveryKeyViewState, onClick = clickLambda) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (state.showSaveConfirmationDialog) { |
|
|
|
if (state.showSaveConfirmationDialog) { |
|
|
|
ConfirmationDialog( |
|
|
|
ConfirmationDialog( |
|
|
@ -134,12 +75,8 @@ private fun SecureBackupSetupState.canGoBack(): Boolean { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Composable |
|
|
|
@Composable |
|
|
|
private fun HeaderContent( |
|
|
|
private fun title(state: SecureBackupSetupState): String { |
|
|
|
state: SecureBackupSetupState, |
|
|
|
return when (state.setupState) { |
|
|
|
modifier: Modifier = Modifier, |
|
|
|
|
|
|
|
) { |
|
|
|
|
|
|
|
val setupState = state.setupState |
|
|
|
|
|
|
|
val title = when (setupState) { |
|
|
|
|
|
|
|
SetupState.Init, |
|
|
|
SetupState.Init, |
|
|
|
SetupState.Creating -> if (state.isChangeRecoveryKeyUserStory) |
|
|
|
SetupState.Creating -> if (state.isChangeRecoveryKeyUserStory) |
|
|
|
stringResource(id = R.string.screen_recovery_key_change_title) |
|
|
|
stringResource(id = R.string.screen_recovery_key_change_title) |
|
|
@ -149,7 +86,11 @@ private fun HeaderContent( |
|
|
|
is SetupState.CreatedAndSaved -> |
|
|
|
is SetupState.CreatedAndSaved -> |
|
|
|
stringResource(id = R.string.screen_recovery_key_save_title) |
|
|
|
stringResource(id = R.string.screen_recovery_key_save_title) |
|
|
|
} |
|
|
|
} |
|
|
|
val subTitle = when (setupState) { |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Composable |
|
|
|
|
|
|
|
private fun subtitle(state: SecureBackupSetupState): String { |
|
|
|
|
|
|
|
return when (state.setupState) { |
|
|
|
SetupState.Init, |
|
|
|
SetupState.Init, |
|
|
|
SetupState.Creating -> if (state.isChangeRecoveryKeyUserStory) |
|
|
|
SetupState.Creating -> if (state.isChangeRecoveryKeyUserStory) |
|
|
|
stringResource(id = R.string.screen_recovery_key_change_description) |
|
|
|
stringResource(id = R.string.screen_recovery_key_change_description) |
|
|
@ -159,25 +100,48 @@ private fun HeaderContent( |
|
|
|
is SetupState.CreatedAndSaved -> |
|
|
|
is SetupState.CreatedAndSaved -> |
|
|
|
stringResource(id = R.string.screen_recovery_key_save_description) |
|
|
|
stringResource(id = R.string.screen_recovery_key_save_description) |
|
|
|
} |
|
|
|
} |
|
|
|
IconTitleSubtitleMolecule( |
|
|
|
} |
|
|
|
modifier = modifier.padding(top = 0.dp), |
|
|
|
|
|
|
|
iconResourceId = CommonDrawables.ic_key, |
|
|
|
@Composable |
|
|
|
title = title, |
|
|
|
private fun Content( |
|
|
|
subTitle = subTitle, |
|
|
|
state: SecureBackupSetupState, |
|
|
|
|
|
|
|
) { |
|
|
|
|
|
|
|
val context = LocalContext.current |
|
|
|
|
|
|
|
val formattedRecoveryKey = state.recoveryKeyViewState.formattedRecoveryKey |
|
|
|
|
|
|
|
val clickLambda = if (formattedRecoveryKey != null) { |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
context.copyToClipboard( |
|
|
|
|
|
|
|
formattedRecoveryKey, |
|
|
|
|
|
|
|
context.getString(R.string.screen_recovery_key_copied_to_clipboard) |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
state.eventSink.invoke(SecureBackupSetupEvents.RecoveryKeyHasBeenSaved) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
if (!state.recoveryKeyViewState.inProgress) { |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
state.eventSink.invoke(SecureBackupSetupEvents.CreateRecoveryKey) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
null |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
RecoveryKeyView( |
|
|
|
|
|
|
|
modifier = Modifier.padding(top = 52.dp), |
|
|
|
|
|
|
|
state = state.recoveryKeyViewState, |
|
|
|
|
|
|
|
onClick = clickLambda, |
|
|
|
|
|
|
|
onChange = null, |
|
|
|
|
|
|
|
onSubmit = null, |
|
|
|
) |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Composable |
|
|
|
@Composable |
|
|
|
private fun BottomMenu( |
|
|
|
private fun ColumnScope.Buttons( |
|
|
|
state: SecureBackupSetupState, |
|
|
|
state: SecureBackupSetupState, |
|
|
|
onSaveClicked: (String) -> Unit, |
|
|
|
|
|
|
|
onDone: () -> Unit, |
|
|
|
onDone: () -> Unit, |
|
|
|
) { |
|
|
|
) { |
|
|
|
val setupState = state.setupState |
|
|
|
val context = LocalContext.current |
|
|
|
ButtonColumnMolecule( |
|
|
|
val chooserTitle = stringResource(id = R.string.screen_recovery_key_save_action) |
|
|
|
modifier = Modifier.padding(bottom = 20.dp) |
|
|
|
when (state.setupState) { |
|
|
|
) { |
|
|
|
|
|
|
|
when (setupState) { |
|
|
|
|
|
|
|
SetupState.Init, |
|
|
|
SetupState.Init, |
|
|
|
SetupState.Creating -> { |
|
|
|
SetupState.Creating -> { |
|
|
|
Button( |
|
|
|
Button( |
|
|
@ -193,31 +157,28 @@ private fun BottomMenu( |
|
|
|
text = stringResource(id = R.string.screen_recovery_key_save_action), |
|
|
|
text = stringResource(id = R.string.screen_recovery_key_save_action), |
|
|
|
leadingIcon = IconSource.Resource(CommonDrawables.ic_compound_download), |
|
|
|
leadingIcon = IconSource.Resource(CommonDrawables.ic_compound_download), |
|
|
|
modifier = Modifier.fillMaxWidth(), |
|
|
|
modifier = Modifier.fillMaxWidth(), |
|
|
|
onClick = { onSaveClicked(setupState.recoveryKey()!!) }, |
|
|
|
onClick = { |
|
|
|
|
|
|
|
context.startSharePlainTextIntent( |
|
|
|
|
|
|
|
activityResultLauncher = null, |
|
|
|
|
|
|
|
chooserTitle = chooserTitle, |
|
|
|
|
|
|
|
text = state.setupState.recoveryKey()!!, |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
state.eventSink.invoke(SecureBackupSetupEvents.RecoveryKeyHasBeenSaved) |
|
|
|
|
|
|
|
}, |
|
|
|
) |
|
|
|
) |
|
|
|
Button( |
|
|
|
Button( |
|
|
|
text = stringResource(id = CommonStrings.action_done), |
|
|
|
text = stringResource(id = CommonStrings.action_done), |
|
|
|
modifier = Modifier.fillMaxWidth(), |
|
|
|
modifier = Modifier.fillMaxWidth(), |
|
|
|
onClick = onDone, |
|
|
|
onClick = { |
|
|
|
) |
|
|
|
if (state.setupState is SetupState.CreatedAndSaved) { |
|
|
|
} |
|
|
|
onDone() |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
state.eventSink.invoke(SecureBackupSetupEvents.Done) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Composable |
|
|
|
|
|
|
|
private fun Content( |
|
|
|
|
|
|
|
state: RecoveryKeyViewState, |
|
|
|
|
|
|
|
onClick: (() -> Unit)?, |
|
|
|
|
|
|
|
) { |
|
|
|
|
|
|
|
val modifier = Modifier.padding(top = 52.dp) |
|
|
|
|
|
|
|
RecoveryKeyView( |
|
|
|
|
|
|
|
modifier = modifier, |
|
|
|
|
|
|
|
state = state, |
|
|
|
|
|
|
|
onClick = onClick, |
|
|
|
|
|
|
|
onChange = null, |
|
|
|
|
|
|
|
onSubmit = null, |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@PreviewsDayNight |
|
|
|
@PreviewsDayNight |
|
|
|