Merge pull request #1737 from vector-im/feature/bma/improvePages
Create UserStoryFlowPage
This commit is contained in:
commit
e2870442c7
9 changed files with 313 additions and 360 deletions
|
|
@ -17,19 +17,16 @@
|
||||||
package io.element.android.features.lockscreen.impl.setup.biometric
|
package io.element.android.features.lockscreen.impl.setup.biometric
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.foundation.layout.Arrangement.spacedBy
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Fingerprint
|
import androidx.compose.material.icons.filled.Fingerprint
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.features.lockscreen.impl.R
|
import io.element.android.features.lockscreen.impl.R
|
||||||
|
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
|
@ -76,10 +73,8 @@ private fun SetupBiometricFooter(
|
||||||
onSkipClicked: () -> Unit,
|
onSkipClicked: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
Column(
|
ButtonColumnMolecule(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier,
|
||||||
verticalArrangement = spacedBy(16.dp),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
) {
|
||||||
val biometricAuth = stringResource(id = R.string.screen_app_lock_biometric_authentication)
|
val biometricAuth = stringResource(id = R.string.screen_app_lock_biometric_authentication)
|
||||||
Button(
|
Button(
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,9 @@ package io.element.android.features.logout.impl
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
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.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
|
@ -29,11 +29,8 @@ import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.libraries.architecture.Async
|
import io.element.android.libraries.architecture.Async
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage
|
||||||
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.ProgressDialog
|
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||||
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
|
||||||
|
|
@ -41,7 +38,6 @@ import io.element.android.libraries.designsystem.theme.components.Button
|
||||||
import io.element.android.libraries.designsystem.theme.components.LinearProgressIndicator
|
import io.element.android.libraries.designsystem.theme.components.LinearProgressIndicator
|
||||||
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.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
|
||||||
import io.element.android.libraries.designsystem.theme.progressIndicatorTrackColor
|
import io.element.android.libraries.designsystem.theme.progressIndicatorTrackColor
|
||||||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||||
import io.element.android.libraries.matrix.api.encryption.BackupUploadState
|
import io.element.android.libraries.matrix.api.encryption.BackupUploadState
|
||||||
|
|
@ -51,7 +47,6 @@ import io.element.android.libraries.testtags.testTag
|
||||||
import io.element.android.libraries.theme.ElementTheme
|
import io.element.android.libraries.theme.ElementTheme
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LogoutView(
|
fun LogoutView(
|
||||||
state: LogoutState,
|
state: LogoutState,
|
||||||
|
|
@ -62,29 +57,23 @@ fun LogoutView(
|
||||||
) {
|
) {
|
||||||
val eventSink = state.eventSink
|
val eventSink = state.eventSink
|
||||||
|
|
||||||
HeaderFooterPage(
|
FlowStepPage(
|
||||||
|
onBackClicked = onBackClicked,
|
||||||
|
title = title(state),
|
||||||
|
subTitle = subtitle(state),
|
||||||
|
iconResourceId = CommonDrawables.ic_key,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
topBar = {
|
content = { Content(state) },
|
||||||
TopAppBar(
|
buttons = {
|
||||||
navigationIcon = { BackButton(onClick = onBackClicked) },
|
Buttons(
|
||||||
title = {},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
header = {
|
|
||||||
HeaderContent(state = state)
|
|
||||||
},
|
|
||||||
footer = {
|
|
||||||
BottomMenu(
|
|
||||||
state = state,
|
state = state,
|
||||||
onChangeRecoveryKeyClicked = onChangeRecoveryKeyClicked,
|
onChangeRecoveryKeyClicked = onChangeRecoveryKeyClicked,
|
||||||
onLogoutClicked = {
|
onLogoutClicked = {
|
||||||
eventSink(LogoutEvents.Logout(ignoreSdkError = false))
|
eventSink(LogoutEvents.Logout(ignoreSdkError = false))
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
) {
|
)
|
||||||
Content(state = state)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log out confirmation dialog
|
// Log out confirmation dialog
|
||||||
if (state.showConfirmationDialog) {
|
if (state.showConfirmationDialog) {
|
||||||
|
|
@ -92,9 +81,6 @@ fun LogoutView(
|
||||||
title = stringResource(id = CommonStrings.action_signout),
|
title = stringResource(id = CommonStrings.action_signout),
|
||||||
content = stringResource(id = R.string.screen_signout_confirmation_dialog_content),
|
content = stringResource(id = R.string.screen_signout_confirmation_dialog_content),
|
||||||
submitText = stringResource(id = CommonStrings.action_signout),
|
submitText = stringResource(id = CommonStrings.action_signout),
|
||||||
onCancelClicked = {
|
|
||||||
eventSink(LogoutEvents.CloseDialogs)
|
|
||||||
},
|
|
||||||
onSubmitClicked = {
|
onSubmitClicked = {
|
||||||
eventSink(LogoutEvents.Logout(ignoreSdkError = false))
|
eventSink(LogoutEvents.Logout(ignoreSdkError = false))
|
||||||
},
|
},
|
||||||
|
|
@ -112,9 +98,6 @@ fun LogoutView(
|
||||||
title = stringResource(id = CommonStrings.dialog_title_error),
|
title = stringResource(id = CommonStrings.dialog_title_error),
|
||||||
content = stringResource(id = CommonStrings.error_unknown),
|
content = stringResource(id = CommonStrings.error_unknown),
|
||||||
submitText = stringResource(id = CommonStrings.action_signout_anyway),
|
submitText = stringResource(id = CommonStrings.action_signout_anyway),
|
||||||
onCancelClicked = {
|
|
||||||
eventSink(LogoutEvents.CloseDialogs)
|
|
||||||
},
|
|
||||||
onSubmitClicked = {
|
onSubmitClicked = {
|
||||||
eventSink(LogoutEvents.Logout(ignoreSdkError = true))
|
eventSink(LogoutEvents.Logout(ignoreSdkError = true))
|
||||||
},
|
},
|
||||||
|
|
@ -132,30 +115,23 @@ fun LogoutView(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun HeaderContent(
|
private fun title(state: LogoutState): String {
|
||||||
state: LogoutState,
|
return when {
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
val title = when {
|
|
||||||
state.backupUploadState.isBackingUp() -> stringResource(id = R.string.screen_signout_key_backup_ongoing_title)
|
state.backupUploadState.isBackingUp() -> stringResource(id = R.string.screen_signout_key_backup_ongoing_title)
|
||||||
state.isLastSession -> stringResource(id = R.string.screen_signout_key_backup_disabled_title)
|
state.isLastSession -> stringResource(id = R.string.screen_signout_key_backup_disabled_title)
|
||||||
else -> stringResource(CommonStrings.action_signout)
|
else -> stringResource(CommonStrings.action_signout)
|
||||||
}
|
}
|
||||||
val subtitle = when {
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun subtitle(state: LogoutState): String? {
|
||||||
|
return when {
|
||||||
(state.backupUploadState as? BackupUploadState.SteadyException)?.exception is SteadyStateException.Connection ->
|
(state.backupUploadState as? BackupUploadState.SteadyException)?.exception is SteadyStateException.Connection ->
|
||||||
stringResource(id = R.string.screen_signout_key_backup_offline_subtitle)
|
stringResource(id = R.string.screen_signout_key_backup_offline_subtitle)
|
||||||
state.backupUploadState.isBackingUp() -> stringResource(id = R.string.screen_signout_key_backup_ongoing_subtitle)
|
state.backupUploadState.isBackingUp() -> stringResource(id = R.string.screen_signout_key_backup_ongoing_subtitle)
|
||||||
state.isLastSession -> stringResource(id = R.string.screen_signout_key_backup_disabled_subtitle)
|
state.isLastSession -> stringResource(id = R.string.screen_signout_key_backup_disabled_subtitle)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
val paddingTop = 0.dp
|
|
||||||
IconTitleSubtitleMolecule(
|
|
||||||
modifier = modifier.padding(top = paddingTop),
|
|
||||||
iconResourceId = CommonDrawables.ic_key,
|
|
||||||
title = title,
|
|
||||||
subTitle = subtitle,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun BackupUploadState.isBackingUp(): Boolean {
|
private fun BackupUploadState.isBackingUp(): Boolean {
|
||||||
|
|
@ -171,37 +147,33 @@ private fun BackupUploadState.isBackingUp(): Boolean {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun BottomMenu(
|
private fun ColumnScope.Buttons(
|
||||||
state: LogoutState,
|
state: LogoutState,
|
||||||
onLogoutClicked: () -> Unit,
|
onLogoutClicked: () -> Unit,
|
||||||
onChangeRecoveryKeyClicked: () -> Unit,
|
onChangeRecoveryKeyClicked: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val logoutAction = state.logoutAction
|
val logoutAction = state.logoutAction
|
||||||
ButtonColumnMolecule(
|
if (state.isLastSession) {
|
||||||
modifier = Modifier.padding(bottom = 20.dp)
|
OutlinedButton(
|
||||||
) {
|
text = stringResource(id = CommonStrings.common_settings),
|
||||||
if (state.isLastSession) {
|
modifier = Modifier.fillMaxWidth(),
|
||||||
OutlinedButton(
|
onClick = onChangeRecoveryKeyClicked,
|
||||||
text = stringResource(id = CommonStrings.common_settings),
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
onClick = onChangeRecoveryKeyClicked,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val signOutSubmitRes = when {
|
|
||||||
logoutAction is Async.Loading -> R.string.screen_signout_in_progress_dialog_content
|
|
||||||
state.backupUploadState.isBackingUp() -> CommonStrings.action_signout_anyway
|
|
||||||
else -> CommonStrings.action_signout
|
|
||||||
}
|
|
||||||
Button(
|
|
||||||
text = stringResource(id = signOutSubmitRes),
|
|
||||||
showProgress = logoutAction is Async.Loading,
|
|
||||||
destructive = true,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.testTag(TestTags.signOut),
|
|
||||||
onClick = onLogoutClicked,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
val signOutSubmitRes = when {
|
||||||
|
logoutAction is Async.Loading -> R.string.screen_signout_in_progress_dialog_content
|
||||||
|
state.backupUploadState.isBackingUp() -> CommonStrings.action_signout_anyway
|
||||||
|
else -> CommonStrings.action_signout
|
||||||
|
}
|
||||||
|
Button(
|
||||||
|
text = stringResource(id = signOutSubmitRes),
|
||||||
|
showProgress = logoutAction is Async.Loading,
|
||||||
|
destructive = true,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.testTag(TestTags.signOut),
|
||||||
|
onClick = onLogoutClicked,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,11 @@ package io.element.android.features.securebackup.impl.disable
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
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.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -31,10 +31,7 @@ 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.libraries.architecture.Async
|
import io.element.android.libraries.architecture.Async
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage
|
||||||
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.components.dialogs.ErrorDialog
|
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
|
@ -42,11 +39,9 @@ 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.Icon
|
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
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.theme.ElementTheme
|
import io.element.android.libraries.theme.ElementTheme
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SecureBackupDisableView(
|
fun SecureBackupDisableView(
|
||||||
state: SecureBackupDisableState,
|
state: SecureBackupDisableState,
|
||||||
|
|
@ -59,23 +54,16 @@ fun SecureBackupDisableView(
|
||||||
onDone()
|
onDone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HeaderFooterPage(
|
FlowStepPage(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
topBar = {
|
onBackClicked = onBackClicked,
|
||||||
TopAppBar(
|
title = stringResource(id = R.string.screen_key_backup_disable_title),
|
||||||
navigationIcon = { BackButton(onClick = onBackClicked) },
|
subTitle = stringResource(id = R.string.screen_key_backup_disable_description),
|
||||||
title = {},
|
iconResourceId = CommonDrawables.ic_key_off,
|
||||||
)
|
content = { Content(state = state) },
|
||||||
},
|
buttons = { Buttons(state = state) },
|
||||||
header = {
|
)
|
||||||
HeaderContent()
|
|
||||||
},
|
|
||||||
footer = {
|
|
||||||
BottomMenu(state = state)
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Content(state = state)
|
|
||||||
}
|
|
||||||
if (state.showConfirmationDialog) {
|
if (state.showConfirmationDialog) {
|
||||||
SecureBackupDisableConfirmationDialog(
|
SecureBackupDisableConfirmationDialog(
|
||||||
onConfirm = { state.eventSink.invoke(SecureBackupDisableEvents.DisableBackup(force = true)) },
|
onConfirm = { state.eventSink.invoke(SecureBackupDisableEvents.DisableBackup(force = true)) },
|
||||||
|
|
@ -102,32 +90,16 @@ private fun SecureBackupDisableConfirmationDialog(onConfirm: () -> Unit, onDismi
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun HeaderContent(
|
private fun ColumnScope.Buttons(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
IconTitleSubtitleMolecule(
|
|
||||||
modifier = modifier.padding(top = 0.dp),
|
|
||||||
iconResourceId = CommonDrawables.ic_key_off,
|
|
||||||
title = stringResource(id = R.string.screen_key_backup_disable_title),
|
|
||||||
subTitle = stringResource(id = R.string.screen_key_backup_disable_description),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun BottomMenu(
|
|
||||||
state: SecureBackupDisableState,
|
state: SecureBackupDisableState,
|
||||||
) {
|
) {
|
||||||
ButtonColumnMolecule(
|
Button(
|
||||||
modifier = Modifier.padding(bottom = 20.dp)
|
text = stringResource(id = R.string.screen_chat_backup_key_backup_action_disable),
|
||||||
) {
|
showProgress = state.disableAction.isLoading(),
|
||||||
Button(
|
destructive = true,
|
||||||
text = stringResource(id = R.string.screen_chat_backup_key_backup_action_disable),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
showProgress = state.disableAction.isLoading(),
|
onClick = { state.eventSink.invoke(SecureBackupDisableEvents.DisableBackup(force = false)) }
|
||||||
destructive = true,
|
)
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
onClick = { state.eventSink.invoke(SecureBackupDisableEvents.DisableBackup(force = false)) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
||||||
|
|
@ -16,29 +16,22 @@
|
||||||
|
|
||||||
package io.element.android.features.securebackup.impl.enable
|
package io.element.android.features.securebackup.impl.enable
|
||||||
|
|
||||||
|
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.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
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.libraries.architecture.Async
|
import io.element.android.libraries.architecture.Async
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage
|
||||||
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.ErrorDialog
|
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||||
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.TopAppBar
|
|
||||||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SecureBackupEnableView(
|
fun SecureBackupEnableView(
|
||||||
state: SecureBackupEnableState,
|
state: SecureBackupEnableState,
|
||||||
|
|
@ -51,20 +44,12 @@ fun SecureBackupEnableView(
|
||||||
onDone()
|
onDone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HeaderFooterPage(
|
FlowStepPage(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
topBar = {
|
onBackClicked = onBackClicked,
|
||||||
TopAppBar(
|
title = stringResource(id = R.string.screen_chat_backup_key_backup_action_enable),
|
||||||
navigationIcon = { BackButton(onClick = onBackClicked) },
|
iconResourceId = CommonDrawables.ic_key,
|
||||||
title = {},
|
buttons = { Buttons(state = state) }
|
||||||
)
|
|
||||||
},
|
|
||||||
header = {
|
|
||||||
HeaderContent()
|
|
||||||
},
|
|
||||||
footer = {
|
|
||||||
BottomMenu(state = state)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
if (state.enableAction is Async.Failure) {
|
if (state.enableAction is Async.Failure) {
|
||||||
ErrorDialog(
|
ErrorDialog(
|
||||||
|
|
@ -75,31 +60,15 @@ fun SecureBackupEnableView(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun HeaderContent(
|
private fun ColumnScope.Buttons(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
IconTitleSubtitleMolecule(
|
|
||||||
modifier = modifier.padding(top = 0.dp),
|
|
||||||
iconResourceId = CommonDrawables.ic_key,
|
|
||||||
title = stringResource(id = R.string.screen_chat_backup_key_backup_action_enable),
|
|
||||||
subTitle = null,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun BottomMenu(
|
|
||||||
state: SecureBackupEnableState,
|
state: SecureBackupEnableState,
|
||||||
) {
|
) {
|
||||||
ButtonColumnMolecule(
|
Button(
|
||||||
modifier = Modifier.padding(bottom = 20.dp)
|
text = stringResource(id = R.string.screen_chat_backup_key_backup_action_enable),
|
||||||
) {
|
showProgress = state.enableAction.isLoading(),
|
||||||
Button(
|
modifier = Modifier.fillMaxWidth(),
|
||||||
text = stringResource(id = R.string.screen_chat_backup_key_backup_action_enable),
|
onClick = { state.eventSink.invoke(SecureBackupEnableEvents.EnableBackup) }
|
||||||
showProgress = state.enableAction.isLoading(),
|
)
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
onClick = { state.eventSink.invoke(SecureBackupEnableEvents.EnableBackup) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,9 @@
|
||||||
|
|
||||||
package io.element.android.features.securebackup.impl.enter
|
package io.element.android.features.securebackup.impl.enter
|
||||||
|
|
||||||
|
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.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
|
@ -26,19 +26,14 @@ 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.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage
|
||||||
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.async.AsyncView
|
import io.element.android.libraries.designsystem.components.async.AsyncView
|
||||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
|
||||||
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.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 SecureBackupEnterRecoveryKeyView(
|
fun SecureBackupEnterRecoveryKeyView(
|
||||||
state: SecureBackupEnterRecoveryKeyState,
|
state: SecureBackupEnterRecoveryKeyState,
|
||||||
|
|
@ -53,79 +48,46 @@ fun SecureBackupEnterRecoveryKeyView(
|
||||||
onErrorDismiss = { state.eventSink(SecureBackupEnterRecoveryKeyEvents.ClearDialog) },
|
onErrorDismiss = { state.eventSink(SecureBackupEnterRecoveryKeyEvents.ClearDialog) },
|
||||||
)
|
)
|
||||||
|
|
||||||
HeaderFooterPage(
|
FlowStepPage(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
topBar = {
|
onBackClicked = onBackClicked,
|
||||||
TopAppBar(
|
|
||||||
navigationIcon = { BackButton(onClick = onBackClicked) },
|
|
||||||
title = {},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
header = {
|
|
||||||
HeaderContent()
|
|
||||||
},
|
|
||||||
footer = {
|
|
||||||
BottomMenu(
|
|
||||||
state = state,
|
|
||||||
onSubmit = {
|
|
||||||
state.eventSink.invoke(SecureBackupEnterRecoveryKeyEvents.Submit)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Content(
|
|
||||||
state = state,
|
|
||||||
onChange = {
|
|
||||||
state.eventSink.invoke(SecureBackupEnterRecoveryKeyEvents.OnRecoveryKeyChange(it))
|
|
||||||
},
|
|
||||||
onSubmit = {
|
|
||||||
state.eventSink.invoke(SecureBackupEnterRecoveryKeyEvents.Submit)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun HeaderContent(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
IconTitleSubtitleMolecule(
|
|
||||||
modifier = modifier.padding(top = 0.dp),
|
|
||||||
iconResourceId = CommonDrawables.ic_key,
|
iconResourceId = CommonDrawables.ic_key,
|
||||||
title = stringResource(id = R.string.screen_recovery_key_confirm_title),
|
title = stringResource(id = R.string.screen_recovery_key_confirm_title),
|
||||||
subTitle = stringResource(id = R.string.screen_recovery_key_confirm_description),
|
subTitle = stringResource(id = R.string.screen_recovery_key_confirm_description),
|
||||||
|
content = { Content(state = state) },
|
||||||
|
buttons = { Buttons(state = state) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun BottomMenu(
|
|
||||||
state: SecureBackupEnterRecoveryKeyState,
|
|
||||||
onSubmit: () -> Unit,
|
|
||||||
) {
|
|
||||||
ButtonColumnMolecule(
|
|
||||||
modifier = Modifier.padding(bottom = 20.dp)
|
|
||||||
) {
|
|
||||||
Button(
|
|
||||||
text = stringResource(id = CommonStrings.action_confirm),
|
|
||||||
enabled = state.isSubmitEnabled,
|
|
||||||
showProgress = state.submitAction.isLoading(),
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
onClick = onSubmit
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun Content(
|
private fun Content(
|
||||||
state: SecureBackupEnterRecoveryKeyState,
|
state: SecureBackupEnterRecoveryKeyState,
|
||||||
onChange: (String) -> Unit,
|
|
||||||
onSubmit: () -> Unit,
|
|
||||||
) {
|
) {
|
||||||
RecoveryKeyView(
|
RecoveryKeyView(
|
||||||
modifier = Modifier.padding(top = 52.dp),
|
modifier = Modifier.padding(top = 52.dp),
|
||||||
state = state.recoveryKeyViewState,
|
state = state.recoveryKeyViewState,
|
||||||
onClick = null,
|
onClick = null,
|
||||||
onChange = onChange,
|
onChange = {
|
||||||
onSubmit = onSubmit,
|
state.eventSink.invoke(SecureBackupEnterRecoveryKeyEvents.OnRecoveryKeyChange(it))
|
||||||
|
},
|
||||||
|
onSubmit = {
|
||||||
|
state.eventSink.invoke(SecureBackupEnterRecoveryKeyEvents.Submit)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ColumnScope.Buttons(
|
||||||
|
state: SecureBackupEnterRecoveryKeyState,
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
text = stringResource(id = CommonStrings.action_confirm),
|
||||||
|
enabled = state.isSubmitEnabled,
|
||||||
|
showProgress = state.submitAction.isLoading(),
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onClick = {
|
||||||
|
state.eventSink.invoke(SecureBackupEnterRecoveryKeyEvents.Submit)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.FlowStepPage
|
||||||
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,15 @@ fun SecureBackupSetupView(
|
||||||
onBackClicked: () -> Unit,
|
onBackClicked: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
FlowStepPage(
|
||||||
val canGoBack = state.canGoBack()
|
|
||||||
BackHandler(enabled = canGoBack) {
|
|
||||||
onBackClicked()
|
|
||||||
}
|
|
||||||
HeaderFooterPage(
|
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
topBar = {
|
onBackClicked = onBackClicked.takeIf { state.canGoBack() },
|
||||||
TopAppBar(
|
title = title(state),
|
||||||
navigationIcon = {
|
subTitle = subtitle(state),
|
||||||
if (canGoBack) {
|
iconResourceId = CommonDrawables.ic_key,
|
||||||
BackButton(onClick = onBackClicked)
|
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 +74,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 +85,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,67 +99,87 @@ 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,
|
|
||||||
title = title,
|
|
||||||
subTitle = subTitle,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun BottomMenu(
|
|
||||||
state: SecureBackupSetupState,
|
|
||||||
onSaveClicked: (String) -> Unit,
|
|
||||||
onDone: () -> Unit,
|
|
||||||
) {
|
|
||||||
val setupState = state.setupState
|
|
||||||
ButtonColumnMolecule(
|
|
||||||
modifier = Modifier.padding(bottom = 20.dp)
|
|
||||||
) {
|
|
||||||
when (setupState) {
|
|
||||||
SetupState.Init,
|
|
||||||
SetupState.Creating -> {
|
|
||||||
Button(
|
|
||||||
text = stringResource(id = CommonStrings.action_done),
|
|
||||||
enabled = false,
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
onClick = onDone
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is SetupState.Created,
|
|
||||||
is SetupState.CreatedAndSaved -> {
|
|
||||||
OutlinedButton(
|
|
||||||
text = stringResource(id = R.string.screen_recovery_key_save_action),
|
|
||||||
leadingIcon = IconSource.Resource(CommonDrawables.ic_compound_download),
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
onClick = { onSaveClicked(setupState.recoveryKey()!!) },
|
|
||||||
)
|
|
||||||
Button(
|
|
||||||
text = stringResource(id = CommonStrings.action_done),
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
onClick = onDone,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun Content(
|
private fun Content(
|
||||||
state: RecoveryKeyViewState,
|
state: SecureBackupSetupState,
|
||||||
onClick: (() -> Unit)?,
|
|
||||||
) {
|
) {
|
||||||
val modifier = Modifier.padding(top = 52.dp)
|
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(
|
RecoveryKeyView(
|
||||||
modifier = modifier,
|
modifier = Modifier.padding(top = 52.dp),
|
||||||
state = state,
|
state = state.recoveryKeyViewState,
|
||||||
onClick = onClick,
|
onClick = clickLambda,
|
||||||
onChange = null,
|
onChange = null,
|
||||||
onSubmit = null,
|
onSubmit = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ColumnScope.Buttons(
|
||||||
|
state: SecureBackupSetupState,
|
||||||
|
onDone: () -> Unit,
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val chooserTitle = stringResource(id = R.string.screen_recovery_key_save_action)
|
||||||
|
when (state.setupState) {
|
||||||
|
SetupState.Init,
|
||||||
|
SetupState.Creating -> {
|
||||||
|
Button(
|
||||||
|
text = stringResource(id = CommonStrings.action_done),
|
||||||
|
enabled = false,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onClick = onDone
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is SetupState.Created,
|
||||||
|
is SetupState.CreatedAndSaved -> {
|
||||||
|
OutlinedButton(
|
||||||
|
text = stringResource(id = R.string.screen_recovery_key_save_action),
|
||||||
|
leadingIcon = IconSource.Resource(CommonDrawables.ic_compound_download),
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onClick = {
|
||||||
|
context.startSharePlainTextIntent(
|
||||||
|
activityResultLauncher = null,
|
||||||
|
chooserTitle = chooserTitle,
|
||||||
|
text = state.setupState.recoveryKey()!!,
|
||||||
|
)
|
||||||
|
state.eventSink.invoke(SecureBackupSetupEvents.RecoveryKeyHasBeenSaved)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
Button(
|
||||||
|
text = stringResource(id = CommonStrings.action_done),
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onClick = {
|
||||||
|
if (state.setupState is SetupState.CreatedAndSaved) {
|
||||||
|
onDone()
|
||||||
|
} else {
|
||||||
|
state.eventSink.invoke(SecureBackupSetupEvents.Done)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun SecureBackupSetupViewPreview(
|
internal fun SecureBackupSetupViewPreview(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.designsystem.atomic.pages
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import io.element.android.libraries.designsystem.R
|
||||||
|
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
||||||
|
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||||
|
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
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.Text
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
|
import io.element.android.libraries.theme.ElementTheme
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Page with:
|
||||||
|
* - a top bar as TobAppBar with optional back button (displayed if [onBackClicked] is not null)
|
||||||
|
* - a header, as IconTitleSubtitleMolecule
|
||||||
|
* - a content.
|
||||||
|
* - a footer, as ButtonColumnMolecule
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun FlowStepPage(
|
||||||
|
iconResourceId: Int?,
|
||||||
|
title: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onBackClicked: (() -> Unit)? = null,
|
||||||
|
subTitle: String? = null,
|
||||||
|
content: @Composable () -> Unit = {},
|
||||||
|
buttons: @Composable ColumnScope.() -> Unit = {},
|
||||||
|
) {
|
||||||
|
BackHandler(enabled = onBackClicked != null) {
|
||||||
|
onBackClicked?.invoke()
|
||||||
|
}
|
||||||
|
HeaderFooterPage(
|
||||||
|
modifier = modifier,
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
navigationIcon = {
|
||||||
|
if (onBackClicked != null) {
|
||||||
|
BackButton(onClick = onBackClicked)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
header = {
|
||||||
|
IconTitleSubtitleMolecule(
|
||||||
|
iconResourceId = iconResourceId,
|
||||||
|
title = title,
|
||||||
|
subTitle = subTitle,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
content = content,
|
||||||
|
footer = {
|
||||||
|
ButtonColumnMolecule(
|
||||||
|
modifier = Modifier.padding(bottom = 20.dp)
|
||||||
|
) {
|
||||||
|
buttons()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun FlowStepPagePreview() = ElementPreview {
|
||||||
|
FlowStepPage(
|
||||||
|
onBackClicked = {},
|
||||||
|
title = "Title",
|
||||||
|
subTitle = "Subtitle",
|
||||||
|
iconResourceId = R.drawable.ic_compound_computer,
|
||||||
|
content = {
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Content",
|
||||||
|
style = ElementTheme.typography.fontHeadingXlBold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buttons = {
|
||||||
|
TextButton(text = "A button", onClick = { })
|
||||||
|
Button(text = "Continue", onClick = { })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:735eecae838f9ea7413d44e33d1ff7b7ce8524a7ee9c601d889f2a23cd06dbee
|
||||||
|
size 18530
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:e1ec13019a59463953135a6b469bcaeed3e61f868efd45c8f7b612a6d96ba711
|
||||||
|
size 17672
|
||||||
Loading…
Add table
Add a link
Reference in a new issue