Merge pull request #1648 from vector-im/feature/bma/secureBackup

Secure backup
This commit is contained in:
Benoit Marty
2023-10-30 21:29:54 +01:00
committed by GitHub
77 changed files with 284 additions and 158 deletions

View File

@@ -1,22 +0,0 @@
/*
* 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.features.securebackup.impl
// TODO Move to appconfig module when it will be available
object SecureBackupConfig {
const val LearnMoreUrl: String = "https://element.io/help#encryption5"
}

View File

@@ -40,6 +40,7 @@ class SecureBackupDisableNode @AssistedInject constructor(
state = state,
modifier = modifier,
onDone = ::navigateUp,
onBackClicked = ::navigateUp,
)
}
}

View File

@@ -22,6 +22,7 @@ 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
@@ -33,6 +34,7 @@ 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.components.dialogs.ConfirmationDialog
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
import io.element.android.libraries.designsystem.preview.ElementPreview
@@ -40,13 +42,16 @@ 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,
onDone: () -> Unit,
onBackClicked: () -> Unit,
modifier: Modifier = Modifier,
) {
LaunchedEffect(state.disableAction) {
@@ -56,6 +61,12 @@ fun SecureBackupDisableView(
}
HeaderFooterPage(
modifier = modifier,
topBar = {
TopAppBar(
navigationIcon = { BackButton(onClick = onBackClicked) },
title = {},
)
},
header = {
HeaderContent()
},
@@ -95,7 +106,7 @@ private fun HeaderContent(
modifier: Modifier = Modifier,
) {
IconTitleSubtitleMolecule(
modifier = modifier.padding(top = 60.dp),
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),
@@ -158,5 +169,6 @@ internal fun SecureBackupDisableViewPreview(
SecureBackupDisableView(
state = state,
onDone = {},
onBackClicked = {},
)
}

View File

@@ -40,6 +40,7 @@ class SecureBackupEnableNode @AssistedInject constructor(
state = state,
modifier = modifier,
onDone = ::navigateUp,
onBackClicked = ::navigateUp,
)
}
}

View File

@@ -18,6 +18,7 @@ package io.element.android.features.securebackup.impl.enable
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
@@ -29,16 +30,20 @@ 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.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,
onDone: () -> Unit,
onBackClicked: () -> Unit,
modifier: Modifier = Modifier,
) {
LaunchedEffect(state.enableAction) {
@@ -48,6 +53,12 @@ fun SecureBackupEnableView(
}
HeaderFooterPage(
modifier = modifier,
topBar = {
TopAppBar(
navigationIcon = { BackButton(onClick = onBackClicked) },
title = {},
)
},
header = {
HeaderContent()
},
@@ -68,7 +79,7 @@ private fun HeaderContent(
modifier: Modifier = Modifier,
) {
IconTitleSubtitleMolecule(
modifier = modifier.padding(top = 60.dp),
modifier = modifier.padding(top = 0.dp),
iconResourceId = CommonDrawables.ic_key,
title = stringResource(id = R.string.screen_chat_backup_key_backup_action_enable),
subTitle = null,
@@ -99,5 +110,6 @@ internal fun SecureBackupEnableViewPreview(
SecureBackupEnableView(
state = state,
onDone = {},
onBackClicked = {},
)
}

View File

@@ -50,7 +50,8 @@ class SecureBackupEnterRecoveryKeyNode @AssistedInject constructor(
onDone = {
coroutineScope.postSuccessSnackbar()
navigateUp()
}
},
onBackClicked = ::navigateUp,
)
}

View File

@@ -18,6 +18,7 @@ package io.element.android.features.securebackup.impl.enter
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
@@ -30,17 +31,21 @@ 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.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
import io.element.android.libraries.ui.strings.CommonStrings
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SecureBackupEnterRecoveryKeyView(
state: SecureBackupEnterRecoveryKeyState,
onDone: () -> Unit,
onBackClicked: () -> Unit,
modifier: Modifier = Modifier,
) {
when (state.submitAction) {
@@ -59,6 +64,12 @@ fun SecureBackupEnterRecoveryKeyView(
HeaderFooterPage(
modifier = modifier,
topBar = {
TopAppBar(
navigationIcon = { BackButton(onClick = onBackClicked) },
title = {},
)
},
header = {
HeaderContent()
},
@@ -75,6 +86,9 @@ fun SecureBackupEnterRecoveryKeyView(
state = state,
onChange = {
state.eventSink.invoke(SecureBackupEnterRecoveryKeyEvents.OnRecoveryKeyChange(it))
},
onSubmit = {
state.eventSink.invoke(SecureBackupEnterRecoveryKeyEvents.Submit)
})
}
}
@@ -84,7 +98,7 @@ private fun HeaderContent(
modifier: Modifier = Modifier,
) {
IconTitleSubtitleMolecule(
modifier = modifier.padding(top = 60.dp),
modifier = modifier.padding(top = 0.dp),
iconResourceId = CommonDrawables.ic_key,
title = stringResource(id = R.string.screen_recovery_key_confirm_title),
subTitle = stringResource(id = R.string.screen_recovery_key_confirm_description),
@@ -112,13 +126,15 @@ private fun BottomMenu(
@Composable
private fun Content(
state: SecureBackupEnterRecoveryKeyState,
onChange: ((String) -> Unit)?,
onChange: (String) -> Unit,
onSubmit: () -> Unit,
) {
RecoveryKeyView(
modifier = Modifier.padding(top = 52.dp),
state = state.recoveryKeyViewState,
onClick = null,
onChange = onChange,
onSubmit = onSubmit,
)
}
@@ -130,5 +146,6 @@ internal fun SecureBackupEnterRecoveryKeyViewPreview(
SecureBackupEnterRecoveryKeyView(
state = state,
onDone = {},
onBackClicked = {},
)
}

View File

@@ -27,7 +27,7 @@ import com.bumble.appyx.core.plugin.plugins
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.securebackup.impl.SecureBackupConfig
import io.element.android.appconfig.SecureBackupConfig
import io.element.android.libraries.di.SessionScope
@ContributesNode(SessionScope::class)

View File

@@ -60,6 +60,7 @@ class SecureBackupSetupNode @AssistedInject constructor(
coroutineScope.postSuccessSnackbar()
navigateUp()
},
onBackClicked = ::navigateUp,
modifier = modifier,
)
}

View File

@@ -16,8 +16,10 @@
package io.element.android.features.securebackup.impl.setup
import androidx.activity.compose.BackHandler
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
@@ -32,24 +34,42 @@ import io.element.android.libraries.androidutils.system.startSharePlainTextInten
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.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,
onDone: () -> Unit,
onBackClicked: () -> Unit,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
val canGoBack = state.canGoBack()
BackHandler(enabled = canGoBack) {
onBackClicked()
}
HeaderFooterPage(
modifier = modifier,
topBar = {
TopAppBar(
navigationIcon = {
if (canGoBack) {
BackButton(onClick = onBackClicked)
}
},
title = {},
)
},
header = {
HeaderContent(state = state)
},
@@ -109,6 +129,10 @@ fun SecureBackupSetupView(
}
}
private fun SecureBackupSetupState.canGoBack(): Boolean {
return recoveryKeyViewState.formattedRecoveryKey == null
}
@Composable
private fun HeaderContent(
state: SecureBackupSetupState,
@@ -136,7 +160,7 @@ private fun HeaderContent(
stringResource(id = R.string.screen_recovery_key_save_description)
}
IconTitleSubtitleMolecule(
modifier = modifier.padding(top = 60.dp),
modifier = modifier.padding(top = 0.dp),
iconResourceId = CommonDrawables.ic_key,
title = title,
subTitle = subTitle,
@@ -192,6 +216,7 @@ private fun Content(
state = state,
onClick = onClick,
onChange = null,
onSubmit = null,
)
}
@@ -203,5 +228,6 @@ internal fun SecureBackupSetupViewPreview(
SecureBackupSetupView(
state = state,
onDone = {},
onBackClicked = {},
)
}

View File

@@ -33,5 +33,6 @@ internal fun SecureBackupSetupViewChangePreview(
recoveryKeyViewState = state.recoveryKeyViewState.copy(recoveryKeyUserStory = RecoveryKeyUserStory.Change),
),
onDone = {},
onBackClicked = {},
)
}

View File

@@ -25,12 +25,15 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.progressSemantics
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
@@ -52,6 +55,7 @@ internal fun RecoveryKeyView(
state: RecoveryKeyViewState,
onClick: (() -> Unit)?,
onChange: ((String) -> Unit)?,
onSubmit: (() -> Unit)?,
modifier: Modifier = Modifier,
) {
Column(
@@ -63,7 +67,7 @@ internal fun RecoveryKeyView(
modifier = Modifier.padding(start = 16.dp),
style = ElementTheme.typography.fontBodyMdRegular,
)
RecoveryKeyContent(state, onClick, onChange)
RecoveryKeyContent(state, onClick, onChange, onSubmit)
RecoveryKeyFooter(state)
}
}
@@ -73,11 +77,12 @@ private fun RecoveryKeyContent(
state: RecoveryKeyViewState,
onClick: (() -> Unit)?,
onChange: ((String) -> Unit)?,
onSubmit: (() -> Unit)?,
) {
when (state.recoveryKeyUserStory) {
RecoveryKeyUserStory.Setup,
RecoveryKeyUserStory.Change -> RecoveryKeyStaticContent(state, onClick)
RecoveryKeyUserStory.Enter -> RecoveryKeyFormContent(state, onChange)
RecoveryKeyUserStory.Enter -> RecoveryKeyFormContent(state, onChange, onSubmit)
}
}
@@ -143,8 +148,13 @@ private fun RecoveryKeyStaticContent(
}
@Composable
private fun RecoveryKeyFormContent(state: RecoveryKeyViewState, onChange: ((String) -> Unit)?) {
private fun RecoveryKeyFormContent(
state: RecoveryKeyViewState,
onChange: ((String) -> Unit)?,
onSubmit: (() -> Unit)?,
) {
onChange ?: error("onChange should not be null")
onSubmit ?: error("onSubmit should not be null")
val recoveryKeyVisualTransformation = remember {
RecoveryKeyVisualTransformation()
}
@@ -155,6 +165,12 @@ private fun RecoveryKeyFormContent(state: RecoveryKeyViewState, onChange: ((Stri
onValueChange = onChange,
enabled = state.inProgress.not(),
visualTransformation = recoveryKeyVisualTransformation,
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Done,
),
keyboardActions = KeyboardActions(
onDone = { onSubmit() }
),
label = { Text(text = stringResource(id = R.string.screen_recovery_key_confirm_key_placeholder)) }
)
}
@@ -217,5 +233,6 @@ internal fun RecoveryKeyViewPreview(
state = state,
onClick = {},
onChange = {},
onSubmit = {},
)
}