Merge pull request #1737 from vector-im/feature/bma/improvePages
Create UserStoryFlowPage
This commit is contained in:
commit
2de508c6d5
9 changed files with 313 additions and 360 deletions
|
|
@ -17,19 +17,16 @@
|
|||
package io.element.android.features.lockscreen.impl.setup.biometric
|
||||
|
||||
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.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Fingerprint
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
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.pages.HeaderFooterPage
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
|
|
@ -76,10 +73,8 @@ private fun SetupBiometricFooter(
|
|||
onSkipClicked: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
verticalArrangement = spacedBy(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
ButtonColumnMolecule(
|
||||
modifier = modifier,
|
||||
) {
|
||||
val biometricAuth = stringResource(id = R.string.screen_app_lock_biometric_authentication)
|
||||
Button(
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ package io.element.android.features.logout.impl
|
|||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
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.LaunchedEffect
|
||||
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.unit.dp
|
||||
import io.element.android.libraries.architecture.Async
|
||||
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.pages.HeaderFooterPage
|
||||
import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage
|
||||
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.preview.ElementPreview
|
||||
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.OutlinedButton
|
||||
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.utils.CommonDrawables
|
||||
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.ui.strings.CommonStrings
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun LogoutView(
|
||||
state: LogoutState,
|
||||
|
|
@ -62,29 +57,23 @@ fun LogoutView(
|
|||
) {
|
||||
val eventSink = state.eventSink
|
||||
|
||||
HeaderFooterPage(
|
||||
FlowStepPage(
|
||||
onBackClicked = onBackClicked,
|
||||
title = title(state),
|
||||
subTitle = subtitle(state),
|
||||
iconResourceId = CommonDrawables.ic_key,
|
||||
modifier = modifier,
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
navigationIcon = { BackButton(onClick = onBackClicked) },
|
||||
title = {},
|
||||
)
|
||||
},
|
||||
header = {
|
||||
HeaderContent(state = state)
|
||||
},
|
||||
footer = {
|
||||
BottomMenu(
|
||||
content = { Content(state) },
|
||||
buttons = {
|
||||
Buttons(
|
||||
state = state,
|
||||
onChangeRecoveryKeyClicked = onChangeRecoveryKeyClicked,
|
||||
onLogoutClicked = {
|
||||
eventSink(LogoutEvents.Logout(ignoreSdkError = false))
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
) {
|
||||
Content(state = state)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// Log out confirmation dialog
|
||||
if (state.showConfirmationDialog) {
|
||||
|
|
@ -92,9 +81,6 @@ fun LogoutView(
|
|||
title = stringResource(id = CommonStrings.action_signout),
|
||||
content = stringResource(id = R.string.screen_signout_confirmation_dialog_content),
|
||||
submitText = stringResource(id = CommonStrings.action_signout),
|
||||
onCancelClicked = {
|
||||
eventSink(LogoutEvents.CloseDialogs)
|
||||
},
|
||||
onSubmitClicked = {
|
||||
eventSink(LogoutEvents.Logout(ignoreSdkError = false))
|
||||
},
|
||||
|
|
@ -112,9 +98,6 @@ fun LogoutView(
|
|||
title = stringResource(id = CommonStrings.dialog_title_error),
|
||||
content = stringResource(id = CommonStrings.error_unknown),
|
||||
submitText = stringResource(id = CommonStrings.action_signout_anyway),
|
||||
onCancelClicked = {
|
||||
eventSink(LogoutEvents.CloseDialogs)
|
||||
},
|
||||
onSubmitClicked = {
|
||||
eventSink(LogoutEvents.Logout(ignoreSdkError = true))
|
||||
},
|
||||
|
|
@ -132,30 +115,23 @@ fun LogoutView(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun HeaderContent(
|
||||
state: LogoutState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val title = when {
|
||||
private fun title(state: LogoutState): String {
|
||||
return when {
|
||||
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)
|
||||
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 ->
|
||||
stringResource(id = R.string.screen_signout_key_backup_offline_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)
|
||||
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 {
|
||||
|
|
@ -171,37 +147,33 @@ private fun BackupUploadState.isBackingUp(): Boolean {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun BottomMenu(
|
||||
private fun ColumnScope.Buttons(
|
||||
state: LogoutState,
|
||||
onLogoutClicked: () -> Unit,
|
||||
onChangeRecoveryKeyClicked: () -> Unit,
|
||||
) {
|
||||
val logoutAction = state.logoutAction
|
||||
ButtonColumnMolecule(
|
||||
modifier = Modifier.padding(bottom = 20.dp)
|
||||
) {
|
||||
if (state.isLastSession) {
|
||||
OutlinedButton(
|
||||
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,
|
||||
if (state.isLastSession) {
|
||||
OutlinedButton(
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@ package io.element.android.features.securebackup.impl.disable
|
|||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -31,10 +31,7 @@ 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.libraries.architecture.Async
|
||||
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.pages.HeaderFooterPage
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||
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.Icon
|
||||
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.theme.ElementTheme
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SecureBackupDisableView(
|
||||
state: SecureBackupDisableState,
|
||||
|
|
@ -59,23 +54,16 @@ fun SecureBackupDisableView(
|
|||
onDone()
|
||||
}
|
||||
}
|
||||
HeaderFooterPage(
|
||||
FlowStepPage(
|
||||
modifier = modifier,
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
navigationIcon = { BackButton(onClick = onBackClicked) },
|
||||
title = {},
|
||||
)
|
||||
},
|
||||
header = {
|
||||
HeaderContent()
|
||||
},
|
||||
footer = {
|
||||
BottomMenu(state = state)
|
||||
}
|
||||
) {
|
||||
Content(state = state)
|
||||
}
|
||||
onBackClicked = onBackClicked,
|
||||
title = stringResource(id = R.string.screen_key_backup_disable_title),
|
||||
subTitle = stringResource(id = R.string.screen_key_backup_disable_description),
|
||||
iconResourceId = CommonDrawables.ic_key_off,
|
||||
content = { Content(state = state) },
|
||||
buttons = { Buttons(state = state) },
|
||||
)
|
||||
|
||||
if (state.showConfirmationDialog) {
|
||||
SecureBackupDisableConfirmationDialog(
|
||||
onConfirm = { state.eventSink.invoke(SecureBackupDisableEvents.DisableBackup(force = true)) },
|
||||
|
|
@ -102,32 +90,16 @@ private fun SecureBackupDisableConfirmationDialog(onConfirm: () -> Unit, onDismi
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun HeaderContent(
|
||||
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(
|
||||
private fun ColumnScope.Buttons(
|
||||
state: SecureBackupDisableState,
|
||||
) {
|
||||
ButtonColumnMolecule(
|
||||
modifier = Modifier.padding(bottom = 20.dp)
|
||||
) {
|
||||
Button(
|
||||
text = stringResource(id = R.string.screen_chat_backup_key_backup_action_disable),
|
||||
showProgress = state.disableAction.isLoading(),
|
||||
destructive = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = { state.eventSink.invoke(SecureBackupDisableEvents.DisableBackup(force = false)) }
|
||||
)
|
||||
}
|
||||
Button(
|
||||
text = stringResource(id = R.string.screen_chat_backup_key_backup_action_disable),
|
||||
showProgress = state.disableAction.isLoading(),
|
||||
destructive = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = { state.eventSink.invoke(SecureBackupDisableEvents.DisableBackup(force = false)) }
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -16,29 +16,22 @@
|
|||
|
||||
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.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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.libraries.architecture.Async
|
||||
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.pages.HeaderFooterPage
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||
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.TopAppBar
|
||||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SecureBackupEnableView(
|
||||
state: SecureBackupEnableState,
|
||||
|
|
@ -51,20 +44,12 @@ fun SecureBackupEnableView(
|
|||
onDone()
|
||||
}
|
||||
}
|
||||
HeaderFooterPage(
|
||||
FlowStepPage(
|
||||
modifier = modifier,
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
navigationIcon = { BackButton(onClick = onBackClicked) },
|
||||
title = {},
|
||||
)
|
||||
},
|
||||
header = {
|
||||
HeaderContent()
|
||||
},
|
||||
footer = {
|
||||
BottomMenu(state = state)
|
||||
}
|
||||
onBackClicked = onBackClicked,
|
||||
title = stringResource(id = R.string.screen_chat_backup_key_backup_action_enable),
|
||||
iconResourceId = CommonDrawables.ic_key,
|
||||
buttons = { Buttons(state = state) }
|
||||
)
|
||||
if (state.enableAction is Async.Failure) {
|
||||
ErrorDialog(
|
||||
|
|
@ -75,31 +60,15 @@ fun SecureBackupEnableView(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun HeaderContent(
|
||||
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(
|
||||
private fun ColumnScope.Buttons(
|
||||
state: SecureBackupEnableState,
|
||||
) {
|
||||
ButtonColumnMolecule(
|
||||
modifier = Modifier.padding(bottom = 20.dp)
|
||||
) {
|
||||
Button(
|
||||
text = stringResource(id = R.string.screen_chat_backup_key_backup_action_enable),
|
||||
showProgress = state.enableAction.isLoading(),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = { state.eventSink.invoke(SecureBackupEnableEvents.EnableBackup) }
|
||||
)
|
||||
}
|
||||
Button(
|
||||
text = stringResource(id = R.string.screen_chat_backup_key_backup_action_enable),
|
||||
showProgress = state.enableAction.isLoading(),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = { state.eventSink.invoke(SecureBackupEnableEvents.EnableBackup) }
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@
|
|||
|
||||
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.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
|
@ -26,19 +26,14 @@ 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.setup.views.RecoveryKeyView
|
||||
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.pages.HeaderFooterPage
|
||||
import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage
|
||||
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.PreviewsDayNight
|
||||
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.ui.strings.CommonStrings
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SecureBackupEnterRecoveryKeyView(
|
||||
state: SecureBackupEnterRecoveryKeyState,
|
||||
|
|
@ -53,79 +48,46 @@ fun SecureBackupEnterRecoveryKeyView(
|
|||
onErrorDismiss = { state.eventSink(SecureBackupEnterRecoveryKeyEvents.ClearDialog) },
|
||||
)
|
||||
|
||||
HeaderFooterPage(
|
||||
FlowStepPage(
|
||||
modifier = modifier,
|
||||
topBar = {
|
||||
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),
|
||||
onBackClicked = onBackClicked,
|
||||
iconResourceId = CommonDrawables.ic_key,
|
||||
title = stringResource(id = R.string.screen_recovery_key_confirm_title),
|
||||
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
|
||||
private fun Content(
|
||||
state: SecureBackupEnterRecoveryKeyState,
|
||||
onChange: (String) -> Unit,
|
||||
onSubmit: () -> Unit,
|
||||
) {
|
||||
RecoveryKeyView(
|
||||
modifier = Modifier.padding(top = 52.dp),
|
||||
state = state.recoveryKeyViewState,
|
||||
onClick = null,
|
||||
onChange = onChange,
|
||||
onSubmit = onSubmit,
|
||||
onChange = {
|
||||
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
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
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.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
|
@ -28,24 +27,18 @@ 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.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.startSharePlainTextIntent
|
||||
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.pages.HeaderFooterPage
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||
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.IconSource
|
||||
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.ui.strings.CommonStrings
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SecureBackupSetupView(
|
||||
state: SecureBackupSetupState,
|
||||
|
|
@ -53,68 +46,15 @@ fun SecureBackupSetupView(
|
|||
onBackClicked: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val canGoBack = state.canGoBack()
|
||||
BackHandler(enabled = canGoBack) {
|
||||
onBackClicked()
|
||||
}
|
||||
HeaderFooterPage(
|
||||
FlowStepPage(
|
||||
modifier = modifier,
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
navigationIcon = {
|
||||
if (canGoBack) {
|
||||
BackButton(onClick = onBackClicked)
|
||||
}
|
||||
},
|
||||
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)
|
||||
}
|
||||
onBackClicked = onBackClicked.takeIf { state.canGoBack() },
|
||||
title = title(state),
|
||||
subTitle = subtitle(state),
|
||||
iconResourceId = CommonDrawables.ic_key,
|
||||
content = { Content(state) },
|
||||
buttons = { Buttons(state, onDone = onDone) },
|
||||
)
|
||||
|
||||
if (state.showSaveConfirmationDialog) {
|
||||
ConfirmationDialog(
|
||||
|
|
@ -134,12 +74,8 @@ private fun SecureBackupSetupState.canGoBack(): Boolean {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun HeaderContent(
|
||||
state: SecureBackupSetupState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val setupState = state.setupState
|
||||
val title = when (setupState) {
|
||||
private fun title(state: SecureBackupSetupState): String {
|
||||
return when (state.setupState) {
|
||||
SetupState.Init,
|
||||
SetupState.Creating -> if (state.isChangeRecoveryKeyUserStory)
|
||||
stringResource(id = R.string.screen_recovery_key_change_title)
|
||||
|
|
@ -149,7 +85,11 @@ private fun HeaderContent(
|
|||
is SetupState.CreatedAndSaved ->
|
||||
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.Creating -> if (state.isChangeRecoveryKeyUserStory)
|
||||
stringResource(id = R.string.screen_recovery_key_change_description)
|
||||
|
|
@ -159,67 +99,87 @@ private fun HeaderContent(
|
|||
is SetupState.CreatedAndSaved ->
|
||||
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
|
||||
private fun Content(
|
||||
state: RecoveryKeyViewState,
|
||||
onClick: (() -> Unit)?,
|
||||
state: SecureBackupSetupState,
|
||||
) {
|
||||
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(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
onClick = onClick,
|
||||
modifier = Modifier.padding(top = 52.dp),
|
||||
state = state.recoveryKeyViewState,
|
||||
onClick = clickLambda,
|
||||
onChange = 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
|
||||
@Composable
|
||||
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