Fix most review comments
This commit is contained in:
parent
08ed272991
commit
9c09f96c0b
14 changed files with 136 additions and 78 deletions
|
|
@ -23,6 +23,7 @@ import io.element.android.libraries.matrix.api.encryption.IdentityResetHandle
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.filterIsInstance
|
import kotlinx.coroutines.flow.filterIsInstance
|
||||||
|
|
@ -37,9 +38,10 @@ class ResetIdentityFlowManager @Inject constructor(
|
||||||
) {
|
) {
|
||||||
private val resetHandleFlow: MutableStateFlow<AsyncData<IdentityResetHandle>> = MutableStateFlow(AsyncData.Uninitialized)
|
private val resetHandleFlow: MutableStateFlow<AsyncData<IdentityResetHandle>> = MutableStateFlow(AsyncData.Uninitialized)
|
||||||
val currentHandleFlow: StateFlow<AsyncData<IdentityResetHandle>> = resetHandleFlow
|
val currentHandleFlow: StateFlow<AsyncData<IdentityResetHandle>> = resetHandleFlow
|
||||||
|
private var whenResetIsDoneWaitingJob: Job? = null
|
||||||
|
|
||||||
fun whenResetIsDone(block: () -> Unit) {
|
fun whenResetIsDone(block: () -> Unit) {
|
||||||
sessionCoroutineScope.launch {
|
whenResetIsDoneWaitingJob = sessionCoroutineScope.launch {
|
||||||
sessionVerificationService.sessionVerifiedStatus.filterIsInstance<SessionVerifiedStatus.Verified>().first()
|
sessionVerificationService.sessionVerifiedStatus.filterIsInstance<SessionVerifiedStatus.Verified>().first()
|
||||||
block()
|
block()
|
||||||
}
|
}
|
||||||
|
|
@ -70,5 +72,8 @@ class ResetIdentityFlowManager @Inject constructor(
|
||||||
suspend fun cancel() {
|
suspend fun cancel() {
|
||||||
currentHandleFlow.value.dataOrNull()?.cancel()
|
currentHandleFlow.value.dataOrNull()?.cancel()
|
||||||
resetHandleFlow.value = AsyncData.Uninitialized
|
resetHandleFlow.value = AsyncData.Uninitialized
|
||||||
|
|
||||||
|
whenResetIsDoneWaitingJob?.cancel()
|
||||||
|
whenResetIsDoneWaitingJob = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
|
|
@ -45,14 +46,13 @@ import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
import io.element.android.libraries.matrix.api.encryption.IdentityOidcResetHandle
|
import io.element.android.libraries.matrix.api.encryption.IdentityOidcResetHandle
|
||||||
import io.element.android.libraries.matrix.api.encryption.IdentityPasswordResetHandle
|
import io.element.android.libraries.matrix.api.encryption.IdentityPasswordResetHandle
|
||||||
import io.element.android.libraries.matrix.api.encryption.IdentityResetHandle
|
|
||||||
import io.element.android.libraries.oidc.api.OidcEntryPoint
|
import io.element.android.libraries.oidc.api.OidcEntryPoint
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.filterIsInstance
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
@ContributesNode(SessionScope::class)
|
@ContributesNode(SessionScope::class)
|
||||||
class ResetIdentityFlowNode @AssistedInject constructor(
|
class ResetIdentityFlowNode @AssistedInject constructor(
|
||||||
|
|
@ -94,7 +94,7 @@ class ResetIdentityFlowNode @AssistedInject constructor(
|
||||||
lifecycle.addObserver(object : DefaultLifecycleObserver {
|
lifecycle.addObserver(object : DefaultLifecycleObserver {
|
||||||
override fun onStart(owner: LifecycleOwner) {
|
override fun onStart(owner: LifecycleOwner) {
|
||||||
// If the custom tab was opened, we need to cancel the reset job
|
// If the custom tab was opened, we need to cancel the reset job
|
||||||
// when we come back to the node if it the reset wasn't successful
|
// when we come back to the node if the reset wasn't successful
|
||||||
cancelResetJob()
|
cancelResetJob()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -129,22 +129,29 @@ class ResetIdentityFlowNode @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.startReset() = launch {
|
private fun CoroutineScope.startReset() = launch {
|
||||||
val handle = resetIdentityFlowManager.getResetHandle()
|
resetIdentityFlowManager.getResetHandle()
|
||||||
.filterIsInstance<AsyncData.Success<IdentityResetHandle>>()
|
.collectLatest { state ->
|
||||||
.first()
|
when (state) {
|
||||||
.data
|
is AsyncData.Failure -> {
|
||||||
|
cancelResetJob()
|
||||||
when (handle) {
|
Timber.e(state.error, "Could not load the reset identity handle.")
|
||||||
is IdentityOidcResetHandle -> {
|
}
|
||||||
if (oidcEntryPoint.canUseCustomTab()) {
|
is AsyncData.Success -> {
|
||||||
activity.openUrlInChromeCustomTab(null, false, handle.url)
|
when (val handle = state.data) {
|
||||||
} else {
|
is IdentityOidcResetHandle -> {
|
||||||
backstack.push(NavTarget.ResetOidc(handle.url))
|
if (oidcEntryPoint.canUseCustomTab()) {
|
||||||
|
activity.openUrlInChromeCustomTab(null, false, handle.url)
|
||||||
|
} else {
|
||||||
|
backstack.push(NavTarget.ResetOidc(handle.url))
|
||||||
|
}
|
||||||
|
resetJob = launch { handle.resetOidc() }
|
||||||
|
}
|
||||||
|
is IdentityPasswordResetHandle -> backstack.push(NavTarget.ResetPassword)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> Unit
|
||||||
}
|
}
|
||||||
resetJob = launch { handle.resetOidc() }
|
|
||||||
}
|
}
|
||||||
is IdentityPasswordResetHandle -> backstack.push(NavTarget.ResetPassword)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cancelResetJob() {
|
private fun cancelResetJob() {
|
||||||
|
|
@ -162,7 +169,10 @@ class ResetIdentityFlowNode @AssistedInject constructor(
|
||||||
|
|
||||||
val startResetState by resetIdentityFlowManager.currentHandleFlow.collectAsState()
|
val startResetState by resetIdentityFlowManager.currentHandleFlow.collectAsState()
|
||||||
if (startResetState.isLoading()) {
|
if (startResetState.isLoading()) {
|
||||||
ProgressDialog()
|
ProgressDialog(
|
||||||
|
properties = DialogProperties(dismissOnBackPress = true, dismissOnClickOutside = true),
|
||||||
|
onDismissRequest = { cancelResetJob() }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
BackstackView(modifier)
|
BackstackView(modifier)
|
||||||
|
|
|
||||||
|
|
@ -34,14 +34,14 @@ import io.element.android.libraries.matrix.api.encryption.IdentityPasswordResetH
|
||||||
class ResetIdentityPasswordNode @AssistedInject constructor(
|
class ResetIdentityPasswordNode @AssistedInject constructor(
|
||||||
@Assisted buildContext: BuildContext,
|
@Assisted buildContext: BuildContext,
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
private val coroutineDispatchers: CoroutineDispatchers,
|
coroutineDispatchers: CoroutineDispatchers,
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins) {
|
||||||
data class Inputs(val handle: IdentityPasswordResetHandle) : NodeInputs
|
data class Inputs(val handle: IdentityPasswordResetHandle) : NodeInputs
|
||||||
|
|
||||||
private val presenter by lazy {
|
private val presenter = ResetIdentityPasswordPresenter(
|
||||||
val inputs = inputs<Inputs>()
|
identityPasswordResetHandle = inputs<Inputs>().handle,
|
||||||
ResetIdentityPasswordPresenter(inputs.handle, dispatchers = coroutineDispatchers)
|
dispatchers = coroutineDispatchers
|
||||||
}
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 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
|
||||||
|
*
|
||||||
|
* https://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.reset.password
|
||||||
|
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
|
|
||||||
|
class ResetIdentityPasswordStateProvider : PreviewParameterProvider<ResetIdentityPasswordState> {
|
||||||
|
override val values: Sequence<ResetIdentityPasswordState>
|
||||||
|
get() = sequenceOf(
|
||||||
|
aResetIdentityPasswordState(),
|
||||||
|
aResetIdentityPasswordState(resetAction = AsyncAction.Loading),
|
||||||
|
aResetIdentityPasswordState(resetAction = AsyncAction.Success(Unit)),
|
||||||
|
aResetIdentityPasswordState(resetAction = AsyncAction.Failure(IllegalStateException("Failed"))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun aResetIdentityPasswordState(
|
||||||
|
resetAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
|
eventSink: (ResetIdentityPasswordEvent) -> Unit = {},
|
||||||
|
) = ResetIdentityPasswordState(
|
||||||
|
resetAction = resetAction,
|
||||||
|
eventSink = eventSink,
|
||||||
|
)
|
||||||
|
|
@ -27,12 +27,12 @@ import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
import androidx.compose.ui.text.input.VisualTransformation
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.features.securebackup.impl.R
|
||||||
import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage
|
import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage
|
||||||
import io.element.android.libraries.designsystem.components.BigIcon
|
import io.element.android.libraries.designsystem.components.BigIcon
|
||||||
import io.element.android.libraries.designsystem.components.ProgressDialog
|
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
|
||||||
import io.element.android.libraries.designsystem.components.form.textFieldState
|
import io.element.android.libraries.designsystem.components.form.textFieldState
|
||||||
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
|
||||||
|
|
@ -54,13 +54,19 @@ fun ResetIdentityPasswordView(
|
||||||
FlowStepPage(
|
FlowStepPage(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
iconStyle = BigIcon.Style.Default(CompoundIcons.LockSolid()),
|
iconStyle = BigIcon.Style.Default(CompoundIcons.LockSolid()),
|
||||||
title = stringResource(CommonStrings.screen_reset_encryption_password_title),
|
title = stringResource(R.string.screen_reset_encryption_password_title),
|
||||||
subTitle = stringResource(CommonStrings.screen_reset_encryption_password_subtitle),
|
subTitle = stringResource(R.string.screen_reset_encryption_password_subtitle),
|
||||||
onBackClick = onBack,
|
onBackClick = onBack,
|
||||||
content = {
|
content = {
|
||||||
Content(
|
Content(
|
||||||
text = passwordState.value,
|
text = passwordState.value,
|
||||||
onTextChange = { passwordState.value = it }
|
onTextChange = { newText ->
|
||||||
|
if (state.resetAction.isFailure()) {
|
||||||
|
state.eventSink(ResetIdentityPasswordEvent.DismissError)
|
||||||
|
}
|
||||||
|
passwordState.value = newText
|
||||||
|
},
|
||||||
|
hasError = state.resetAction.isFailure(),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
buttons = {
|
buttons = {
|
||||||
|
|
@ -69,22 +75,19 @@ fun ResetIdentityPasswordView(
|
||||||
text = stringResource(CommonStrings.action_reset_identity),
|
text = stringResource(CommonStrings.action_reset_identity),
|
||||||
onClick = { state.eventSink(ResetIdentityPasswordEvent.Reset(passwordState.value)) },
|
onClick = { state.eventSink(ResetIdentityPasswordEvent.Reset(passwordState.value)) },
|
||||||
destructive = true,
|
destructive = true,
|
||||||
|
enabled = passwordState.value.isNotEmpty(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// On success we need to wait until the screen is automatically dismissed, so we keep the progress dialog
|
||||||
if (state.resetAction.isLoading() || state.resetAction.isSuccess()) {
|
if (state.resetAction.isLoading() || state.resetAction.isSuccess()) {
|
||||||
ProgressDialog()
|
ProgressDialog()
|
||||||
} else if (state.resetAction.isFailure()) {
|
|
||||||
ErrorDialog(
|
|
||||||
content = stringResource(CommonStrings.error_unknown),
|
|
||||||
onDismiss = { state.eventSink(ResetIdentityPasswordEvent.DismissError) }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun Content(text: String, onTextChange: (String) -> Unit) {
|
private fun Content(text: String, onTextChange: (String) -> Unit, hasError: Boolean) {
|
||||||
var showPassword by remember { mutableStateOf(false) }
|
var showPassword by remember { mutableStateOf(false) }
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -93,7 +96,7 @@ private fun Content(text: String, onTextChange: (String) -> Unit) {
|
||||||
value = text,
|
value = text,
|
||||||
onValueChange = onTextChange,
|
onValueChange = onTextChange,
|
||||||
label = { Text(stringResource(CommonStrings.common_password)) },
|
label = { Text(stringResource(CommonStrings.common_password)) },
|
||||||
placeholder = { Text(stringResource(CommonStrings.screen_reset_encryption_password_placeholder)) },
|
placeholder = { Text(stringResource(R.string.screen_reset_encryption_password_placeholder)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(),
|
visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(),
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
|
|
@ -105,19 +108,22 @@ private fun Content(text: String, onTextChange: (String) -> Unit) {
|
||||||
IconButton(onClick = { showPassword = !showPassword }) {
|
IconButton(onClick = { showPassword = !showPassword }) {
|
||||||
Icon(imageVector = image, description)
|
Icon(imageVector = image, description)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
isError = hasError,
|
||||||
|
supportingText = if (hasError) {
|
||||||
|
{ Text(stringResource(R.string.screen_reset_encryption_password_error)) }
|
||||||
|
} else {
|
||||||
|
null
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun ResetIdentityPasswordViewPreview() {
|
internal fun ResetIdentityPasswordViewPreview(@PreviewParameter(ResetIdentityPasswordStateProvider::class) state: ResetIdentityPasswordState) {
|
||||||
ElementPreview {
|
ElementPreview {
|
||||||
ResetIdentityPasswordView(
|
ResetIdentityPasswordView(
|
||||||
state = ResetIdentityPasswordState(
|
state = state,
|
||||||
resetAction = AsyncAction.Uninitialized,
|
|
||||||
eventSink = {}
|
|
||||||
),
|
|
||||||
onBack = {}
|
onBack = {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
|
import io.element.android.features.securebackup.impl.R
|
||||||
import io.element.android.libraries.designsystem.atomic.organisms.InfoListItem
|
import io.element.android.libraries.designsystem.atomic.organisms.InfoListItem
|
||||||
import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrganism
|
import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrganism
|
||||||
import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage
|
import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage
|
||||||
|
|
@ -52,8 +53,8 @@ fun ResetIdentityRootView(
|
||||||
FlowStepPage(
|
FlowStepPage(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
iconStyle = BigIcon.Style.AlertSolid,
|
iconStyle = BigIcon.Style.AlertSolid,
|
||||||
title = stringResource(io.element.android.libraries.ui.strings.R.string.screen_encryption_reset_title),
|
title = stringResource(R.string.screen_encryption_reset_title),
|
||||||
subTitle = stringResource(io.element.android.libraries.ui.strings.R.string.screen_encryption_reset_subtitle),
|
subTitle = stringResource(R.string.screen_encryption_reset_subtitle),
|
||||||
isScrollable = true,
|
isScrollable = true,
|
||||||
content = { Content() },
|
content = { Content() },
|
||||||
buttons = {
|
buttons = {
|
||||||
|
|
@ -69,9 +70,9 @@ fun ResetIdentityRootView(
|
||||||
|
|
||||||
if (state.displayConfirmationDialog) {
|
if (state.displayConfirmationDialog) {
|
||||||
ConfirmationDialog(
|
ConfirmationDialog(
|
||||||
title = stringResource(CommonStrings.screen_reset_encryption_confirmation_alert_title),
|
title = stringResource(R.string.screen_reset_encryption_confirmation_alert_title),
|
||||||
content = stringResource(CommonStrings.screen_reset_encryption_confirmation_alert_subtitle),
|
content = stringResource(R.string.screen_reset_encryption_confirmation_alert_subtitle),
|
||||||
submitText = stringResource(CommonStrings.screen_reset_encryption_confirmation_alert_action),
|
submitText = stringResource(R.string.screen_reset_encryption_confirmation_alert_action),
|
||||||
onSubmitClick = {
|
onSubmitClick = {
|
||||||
state.eventSink(ResetIdentityRootEvent.DismissDialog)
|
state.eventSink(ResetIdentityRootEvent.DismissDialog)
|
||||||
onContinue()
|
onContinue()
|
||||||
|
|
@ -92,7 +93,7 @@ private fun Content() {
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
items = persistentListOf(
|
items = persistentListOf(
|
||||||
InfoListItem(
|
InfoListItem(
|
||||||
message = stringResource(CommonStrings.screen_encryption_reset_bullet_1),
|
message = stringResource(R.string.screen_encryption_reset_bullet_1),
|
||||||
iconComposable = {
|
iconComposable = {
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.size(20.dp),
|
modifier = Modifier.size(20.dp),
|
||||||
|
|
@ -103,7 +104,7 @@ private fun Content() {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
InfoListItem(
|
InfoListItem(
|
||||||
message = stringResource(CommonStrings.screen_encryption_reset_bullet_2),
|
message = stringResource(R.string.screen_encryption_reset_bullet_2),
|
||||||
iconComposable = {
|
iconComposable = {
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.size(20.dp),
|
modifier = Modifier.size(20.dp),
|
||||||
|
|
@ -114,7 +115,7 @@ private fun Content() {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
InfoListItem(
|
InfoListItem(
|
||||||
message = stringResource(CommonStrings.screen_encryption_reset_bullet_3),
|
message = stringResource(R.string.screen_encryption_reset_bullet_3),
|
||||||
iconComposable = {
|
iconComposable = {
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.size(20.dp),
|
modifier = Modifier.size(20.dp),
|
||||||
|
|
@ -130,7 +131,7 @@ private fun Content() {
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
text = stringResource(CommonStrings.screen_encryption_reset_footer),
|
text = stringResource(R.string.screen_encryption_reset_footer),
|
||||||
style = ElementTheme.typography.fontBodyMdMedium,
|
style = ElementTheme.typography.fontBodyMdMedium,
|
||||||
color = ElementTheme.colors.textActionPrimary,
|
color = ElementTheme.colors.textActionPrimary,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,12 @@
|
||||||
<string name="screen_create_new_recovery_key_list_item_4">"Follow the instructions to create a new recovery key"</string>
|
<string name="screen_create_new_recovery_key_list_item_4">"Follow the instructions to create a new recovery key"</string>
|
||||||
<string name="screen_create_new_recovery_key_list_item_5">"Save your new recovery key in a password manager or encrypted note"</string>
|
<string name="screen_create_new_recovery_key_list_item_5">"Save your new recovery key in a password manager or encrypted note"</string>
|
||||||
<string name="screen_create_new_recovery_key_title">"Reset the encryption for your account using another device"</string>
|
<string name="screen_create_new_recovery_key_title">"Reset the encryption for your account using another device"</string>
|
||||||
|
<string name="screen_encryption_reset_bullet_1">"Your account details, contacts, preferences, and chat list will be kept"</string>
|
||||||
|
<string name="screen_encryption_reset_bullet_2">"You will lose your existing message history"</string>
|
||||||
|
<string name="screen_encryption_reset_bullet_3">"You will need to verify all your existing devices and contacts again"</string>
|
||||||
|
<string name="screen_encryption_reset_footer">"Only reset your identity if you don’t have access to another signed-in device and you’ve lost your recovery key."</string>
|
||||||
|
<string name="screen_encryption_reset_subtitle">"If you’re not signed in to any other devices and you’ve lost your recovery key, then you’ll need to reset your identity to continue using the app. "</string>
|
||||||
|
<string name="screen_encryption_reset_title">"Reset your identity in case you can’t confirm another way"</string>
|
||||||
<string name="screen_key_backup_disable_confirmation_action_turn_off">"Turn off"</string>
|
<string name="screen_key_backup_disable_confirmation_action_turn_off">"Turn off"</string>
|
||||||
<string name="screen_key_backup_disable_confirmation_description">"You will lose your encrypted messages if you are signed out of all devices."</string>
|
<string name="screen_key_backup_disable_confirmation_description">"You will lose your encrypted messages if you are signed out of all devices."</string>
|
||||||
<string name="screen_key_backup_disable_confirmation_title">"Are you sure you want to turn off backup?"</string>
|
<string name="screen_key_backup_disable_confirmation_title">"Are you sure you want to turn off backup?"</string>
|
||||||
|
|
@ -51,4 +57,11 @@
|
||||||
<string name="screen_recovery_key_setup_generate_key_description">"Make sure you can store your recovery key somewhere safe"</string>
|
<string name="screen_recovery_key_setup_generate_key_description">"Make sure you can store your recovery key somewhere safe"</string>
|
||||||
<string name="screen_recovery_key_setup_success">"Recovery setup successful"</string>
|
<string name="screen_recovery_key_setup_success">"Recovery setup successful"</string>
|
||||||
<string name="screen_recovery_key_setup_title">"Set up recovery"</string>
|
<string name="screen_recovery_key_setup_title">"Set up recovery"</string>
|
||||||
|
<string name="screen_reset_encryption_confirmation_alert_action">"Yes, reset now"</string>
|
||||||
|
<string name="screen_reset_encryption_confirmation_alert_subtitle">"This process is irreversible."</string>
|
||||||
|
<string name="screen_reset_encryption_confirmation_alert_title">"Are you sure you want to reset your encryption?"</string>
|
||||||
|
<string name="screen_reset_encryption_password_error">"An unknown error happened. Please check your account password is correct and try again."</string>
|
||||||
|
<string name="screen_reset_encryption_password_placeholder">"Enter…"</string>
|
||||||
|
<string name="screen_reset_encryption_password_subtitle">"Confirm that you want to reset your encryption."</string>
|
||||||
|
<string name="screen_reset_encryption_password_title">"Enter your account password to continue"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -76,12 +76,12 @@ class ResetIdentityPasswordViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking OK dismisses the error dialog`() {
|
fun `modifying the password dismisses the error state`() {
|
||||||
val eventsRecorder = EventsRecorder<ResetIdentityPasswordEvent>()
|
val eventsRecorder = EventsRecorder<ResetIdentityPasswordEvent>()
|
||||||
rule.setResetPasswordView(
|
rule.setResetPasswordView(
|
||||||
ResetIdentityPasswordState(resetAction = AsyncAction.Failure(IllegalStateException("A failure")), eventSink = eventsRecorder),
|
ResetIdentityPasswordState(resetAction = AsyncAction.Failure(IllegalStateException("A failure")), eventSink = eventsRecorder),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_ok)
|
rule.onNodeWithText("Password").performTextInput("A password")
|
||||||
|
|
||||||
eventsRecorder.assertSingle(ResetIdentityPasswordEvent.DismissError)
|
eventsRecorder.assertSingle(ResetIdentityPasswordEvent.DismissError)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import io.element.android.features.securebackup.impl.R
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||||
import io.element.android.tests.testutils.EventsRecorder
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
|
|
@ -80,7 +81,7 @@ class ResetIdentityRootViewTest {
|
||||||
ResetIdentityRootState(displayConfirmationDialog = true, eventSink = {}),
|
ResetIdentityRootState(displayConfirmationDialog = true, eventSink = {}),
|
||||||
onContinue = it,
|
onContinue = it,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.screen_reset_encryption_confirmation_alert_action)
|
rule.clickOn(R.string.screen_reset_encryption_confirmation_alert_action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,13 +22,14 @@ import io.element.android.libraries.matrix.api.encryption.EnableRecoveryProgress
|
||||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||||
import io.element.android.libraries.matrix.api.encryption.IdentityResetHandle
|
import io.element.android.libraries.matrix.api.encryption.IdentityResetHandle
|
||||||
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||||
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
import io.element.android.tests.testutils.simulateLongTask
|
import io.element.android.tests.testutils.simulateLongTask
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
|
||||||
class FakeEncryptionService(
|
class FakeEncryptionService(
|
||||||
var startIdentityResetLambda: () -> Result<IdentityResetHandle?> = { error("Not implemented") },
|
var startIdentityResetLambda: () -> Result<IdentityResetHandle?> = { lambdaError() },
|
||||||
) : EncryptionService {
|
) : EncryptionService {
|
||||||
private var disableRecoveryFailure: Exception? = null
|
private var disableRecoveryFailure: Exception? = null
|
||||||
override val backupStateStateFlow: MutableStateFlow<BackupState> = MutableStateFlow(BackupState.UNKNOWN)
|
override val backupStateStateFlow: MutableStateFlow<BackupState> = MutableStateFlow(BackupState.UNKNOWN)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2023 New Vector Ltd
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2022 New Vector Ltd
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -43,13 +43,8 @@ dependencies {
|
||||||
implementation(projects.libraries.androidutils)
|
implementation(projects.libraries.androidutils)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
implementation(projects.libraries.matrix.api)
|
|
||||||
implementation(projects.libraries.network)
|
|
||||||
implementation(projects.libraries.designsystem)
|
implementation(projects.libraries.designsystem)
|
||||||
implementation(projects.libraries.testtags)
|
|
||||||
implementation(projects.libraries.uiStrings)
|
implementation(projects.libraries.uiStrings)
|
||||||
implementation(projects.libraries.permissions.api)
|
|
||||||
implementation(projects.libraries.qrcode)
|
|
||||||
implementation(libs.androidx.browser)
|
implementation(libs.androidx.browser)
|
||||||
implementation(platform(libs.network.retrofit.bom))
|
implementation(platform(libs.network.retrofit.bom))
|
||||||
implementation(libs.network.retrofit)
|
implementation(libs.network.retrofit)
|
||||||
|
|
@ -57,15 +52,12 @@ dependencies {
|
||||||
api(projects.libraries.oidc.api)
|
api(projects.libraries.oidc.api)
|
||||||
|
|
||||||
testImplementation(libs.test.junit)
|
testImplementation(libs.test.junit)
|
||||||
testImplementation(libs.androidx.compose.ui.test.junit)
|
|
||||||
testImplementation(libs.androidx.test.ext.junit)
|
testImplementation(libs.androidx.test.ext.junit)
|
||||||
testImplementation(libs.coroutines.test)
|
testImplementation(libs.coroutines.test)
|
||||||
testImplementation(libs.molecule.runtime)
|
testImplementation(libs.molecule.runtime)
|
||||||
testImplementation(libs.test.robolectric)
|
|
||||||
testImplementation(libs.test.truth)
|
testImplementation(libs.test.truth)
|
||||||
testImplementation(libs.test.turbine)
|
testImplementation(libs.test.turbine)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
testImplementation(projects.libraries.permissions.test)
|
testImplementation(projects.libraries.permissions.test)
|
||||||
testImplementation(projects.tests.testutils)
|
testImplementation(projects.tests.testutils)
|
||||||
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -268,12 +268,6 @@ Reason: %1$s."</string>
|
||||||
<string name="invite_friends_text">"Hey, talk to me on %1$s: %2$s"</string>
|
<string name="invite_friends_text">"Hey, talk to me on %1$s: %2$s"</string>
|
||||||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||||
<string name="preference_rageshake">"Rageshake to report bug"</string>
|
<string name="preference_rageshake">"Rageshake to report bug"</string>
|
||||||
<string name="screen_encryption_reset_bullet_1">"Your account details, contacts, preferences, and chat list will be kept"</string>
|
|
||||||
<string name="screen_encryption_reset_bullet_2">"You will lose your existing message history"</string>
|
|
||||||
<string name="screen_encryption_reset_bullet_3">"You will need to verify all your existing devices and contacts again"</string>
|
|
||||||
<string name="screen_encryption_reset_footer">"Only reset your identity if you don’t have access to another signed-in device and you’ve lost your recovery key."</string>
|
|
||||||
<string name="screen_encryption_reset_subtitle">"If you’re not signed in to any other devices and you’ve lost your recovery key, then you’ll need to reset your identity to continue using the app. "</string>
|
|
||||||
<string name="screen_encryption_reset_title">"Reset your identity in case you can’t confirm another way"</string>
|
|
||||||
<string name="screen_media_picker_error_failed_selection">"Failed selecting media, please try again."</string>
|
<string name="screen_media_picker_error_failed_selection">"Failed selecting media, please try again."</string>
|
||||||
<string name="screen_media_upload_preview_error_failed_processing">"Failed processing media to upload, please try again."</string>
|
<string name="screen_media_upload_preview_error_failed_processing">"Failed processing media to upload, please try again."</string>
|
||||||
<string name="screen_media_upload_preview_error_failed_sending">"Failed uploading media, please try again."</string>
|
<string name="screen_media_upload_preview_error_failed_sending">"Failed uploading media, please try again."</string>
|
||||||
|
|
@ -284,12 +278,6 @@ Reason: %1$s."</string>
|
||||||
<item quantity="other">"%1$d Pinned messages"</item>
|
<item quantity="other">"%1$d Pinned messages"</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="screen_pinned_timeline_screen_title_empty">"Pinned messages"</string>
|
<string name="screen_pinned_timeline_screen_title_empty">"Pinned messages"</string>
|
||||||
<string name="screen_reset_encryption_confirmation_alert_action">"Yes, reset now"</string>
|
|
||||||
<string name="screen_reset_encryption_confirmation_alert_subtitle">"This process is irreversible."</string>
|
|
||||||
<string name="screen_reset_encryption_confirmation_alert_title">"Are you sure you want to reset your encryption?"</string>
|
|
||||||
<string name="screen_reset_encryption_password_placeholder">"Enter…"</string>
|
|
||||||
<string name="screen_reset_encryption_password_subtitle">"Confirm that you want to reset your encryption."</string>
|
|
||||||
<string name="screen_reset_encryption_password_title">"Enter your account password to continue"</string>
|
|
||||||
<string name="screen_room_details_pinned_events_row_title">"Pinned messages"</string>
|
<string name="screen_room_details_pinned_events_row_title">"Pinned messages"</string>
|
||||||
<string name="screen_room_error_failed_processing_media">"Failed processing media to upload, please try again."</string>
|
<string name="screen_room_error_failed_processing_media">"Failed processing media to upload, please try again."</string>
|
||||||
<string name="screen_room_error_failed_retrieving_user_details">"Could not retrieve user details"</string>
|
<string name="screen_room_error_failed_retrieving_user_details">"Could not retrieve user details"</string>
|
||||||
|
|
|
||||||
|
|
@ -210,7 +210,10 @@
|
||||||
"screen_chat_backup_.*",
|
"screen_chat_backup_.*",
|
||||||
"screen_key_backup_disable_.*",
|
"screen_key_backup_disable_.*",
|
||||||
"screen_recovery_key_.*",
|
"screen_recovery_key_.*",
|
||||||
"screen_create_new_recovery_key_.*"
|
"screen_create_new_recovery_key_.*",
|
||||||
|
"screen_encryption_reset.*",
|
||||||
|
"screen_reset_encryption.*",
|
||||||
|
"screen\\.reset_encryption.*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue