Improve APIs, add tests
This commit is contained in:
parent
175bada0df
commit
7fd0ad09dc
22 changed files with 562 additions and 76 deletions
|
|
@ -19,7 +19,6 @@ package io.element.android.features.securebackup.impl.reset
|
|||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
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.SessionVerifiedStatus
|
||||
|
|
@ -46,10 +45,6 @@ class ResetIdentityFlowManager @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun currentSessionId(): SessionId {
|
||||
return matrixClient.sessionId
|
||||
}
|
||||
|
||||
fun getResetHandle(): StateFlow<AsyncData<IdentityResetHandle>> {
|
||||
return if (resetHandleFlow.value.isLoading() || resetHandleFlow.value.isSuccess()) {
|
||||
resetHandleFlow
|
||||
|
|
|
|||
|
|
@ -34,8 +34,8 @@ import com.bumble.appyx.navmodel.backstack.operation.push
|
|||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.features.securebackup.impl.reset.password.ResetKeyPasswordNode
|
||||
import io.element.android.features.securebackup.impl.reset.root.ResetKeyRootNode
|
||||
import io.element.android.features.securebackup.impl.reset.password.ResetIdentityPasswordNode
|
||||
import io.element.android.features.securebackup.impl.reset.root.ResetIdentityRootNode
|
||||
import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.BackstackView
|
||||
|
|
@ -108,18 +108,18 @@ class ResetIdentityFlowNode @AssistedInject constructor(
|
|||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||
return when (navTarget) {
|
||||
is NavTarget.Root -> {
|
||||
val callback = object : ResetKeyRootNode.Callback {
|
||||
val callback = object : ResetIdentityRootNode.Callback {
|
||||
override fun onContinue() {
|
||||
coroutineScope.startReset()
|
||||
}
|
||||
}
|
||||
createNode<ResetKeyRootNode>(buildContext, listOf(callback))
|
||||
createNode<ResetIdentityRootNode>(buildContext, listOf(callback))
|
||||
}
|
||||
is NavTarget.ResetPassword -> {
|
||||
val handle = resetIdentityFlowManager.currentHandleFlow.value.dataOrNull() as? IdentityPasswordResetHandle ?: error("No password handle found")
|
||||
createNode<ResetKeyPasswordNode>(
|
||||
createNode<ResetIdentityPasswordNode>(
|
||||
buildContext,
|
||||
listOf(ResetKeyPasswordNode.Inputs(resetIdentityFlowManager.currentSessionId(), handle))
|
||||
listOf(ResetIdentityPasswordNode.Inputs(handle))
|
||||
)
|
||||
}
|
||||
is NavTarget.ResetOidc -> {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package io.element.android.features.securebackup.impl.reset.password
|
||||
|
||||
sealed interface ResetKeyPasswordEvent {
|
||||
data class Reset(val password: String) : ResetKeyPasswordEvent
|
||||
data object DismissError : ResetKeyPasswordEvent
|
||||
sealed interface ResetIdentityPasswordEvent {
|
||||
data class Reset(val password: String) : ResetIdentityPasswordEvent
|
||||
data object DismissError : ResetIdentityPasswordEvent
|
||||
}
|
||||
|
|
@ -21,33 +21,33 @@ import androidx.compose.ui.Modifier
|
|||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
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.libraries.architecture.NodeInputs
|
||||
import io.element.android.libraries.architecture.inputs
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.encryption.IdentityPasswordResetHandle
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
class ResetKeyPasswordNode @AssistedInject constructor(
|
||||
class ResetIdentityPasswordNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val coroutineDispatchers: CoroutineDispatchers,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
|
||||
data class Inputs(val userId: UserId, val handle: IdentityPasswordResetHandle) : NodeInputs
|
||||
data class Inputs(val handle: IdentityPasswordResetHandle) : NodeInputs
|
||||
|
||||
private val presenter by lazy {
|
||||
val inputs = inputs<Inputs>()
|
||||
ResetKeyPasswordPresenter(inputs.userId, inputs.handle)
|
||||
ResetIdentityPasswordPresenter(inputs.handle, dispatchers = coroutineDispatchers)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
ResetKeyPasswordView(
|
||||
ResetIdentityPasswordView(
|
||||
state = state,
|
||||
onBack = ::navigateUp
|
||||
)
|
||||
|
|
@ -24,37 +24,37 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.matrix.api.encryption.IdentityPasswordResetHandle
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ResetKeyPasswordPresenter(
|
||||
private val userId: UserId,
|
||||
class ResetIdentityPasswordPresenter(
|
||||
private val identityPasswordResetHandle: IdentityPasswordResetHandle,
|
||||
) : Presenter<ResetKeyPasswordState> {
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
) : Presenter<ResetIdentityPasswordState> {
|
||||
@Composable
|
||||
override fun present(): ResetKeyPasswordState {
|
||||
override fun present(): ResetIdentityPasswordState {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
val resetAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
|
||||
|
||||
fun handleEvent(event: ResetKeyPasswordEvent) {
|
||||
fun handleEvent(event: ResetIdentityPasswordEvent) {
|
||||
when (event) {
|
||||
is ResetKeyPasswordEvent.Reset -> coroutineScope.reset(userId, event.password, resetAction)
|
||||
ResetKeyPasswordEvent.DismissError -> resetAction.value = AsyncAction.Uninitialized
|
||||
is ResetIdentityPasswordEvent.Reset -> coroutineScope.reset(event.password, resetAction)
|
||||
ResetIdentityPasswordEvent.DismissError -> resetAction.value = AsyncAction.Uninitialized
|
||||
}
|
||||
}
|
||||
|
||||
return ResetKeyPasswordState(
|
||||
return ResetIdentityPasswordState(
|
||||
resetAction = resetAction.value,
|
||||
eventSink = ::handleEvent
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.reset(userId: UserId, password: String, action: MutableState<AsyncAction<Unit>>) = launch {
|
||||
private fun CoroutineScope.reset(password: String, action: MutableState<AsyncAction<Unit>>) = launch(dispatchers.io) {
|
||||
suspend {
|
||||
identityPasswordResetHandle.resetPassword(userId, password).getOrThrow()
|
||||
identityPasswordResetHandle.resetPassword(password).getOrThrow()
|
||||
}.runCatchingUpdatingState(action)
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ package io.element.android.features.securebackup.impl.reset.password
|
|||
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
|
||||
data class ResetKeyPasswordState(
|
||||
data class ResetIdentityPasswordState(
|
||||
val resetAction: AsyncAction<Unit>,
|
||||
val eventSink: (ResetKeyPasswordEvent) -> Unit,
|
||||
val eventSink: (ResetIdentityPasswordEvent) -> Unit,
|
||||
)
|
||||
|
|
@ -46,8 +46,8 @@ import io.element.android.libraries.designsystem.theme.components.onTabOrEnterKe
|
|||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@Composable
|
||||
fun ResetKeyPasswordView(
|
||||
state: ResetKeyPasswordState,
|
||||
fun ResetIdentityPasswordView(
|
||||
state: ResetIdentityPasswordState,
|
||||
onBack: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
|
@ -63,7 +63,7 @@ fun ResetKeyPasswordView(
|
|||
Button(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(CommonStrings.action_reset_identity),
|
||||
onClick = { state.eventSink(ResetKeyPasswordEvent.Reset(passwordState.value)) },
|
||||
onClick = { state.eventSink(ResetIdentityPasswordEvent.Reset(passwordState.value)) },
|
||||
destructive = true,
|
||||
)
|
||||
}
|
||||
|
|
@ -74,7 +74,7 @@ fun ResetKeyPasswordView(
|
|||
} else if (state.resetAction.isFailure()) {
|
||||
ErrorDialog(
|
||||
content = stringResource(CommonStrings.error_unknown),
|
||||
onDismiss = { state.eventSink(ResetKeyPasswordEvent.DismissError) }
|
||||
onDismiss = { state.eventSink(ResetIdentityPasswordEvent.DismissError) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -107,10 +107,10 @@ private fun Content(textFieldState: MutableState<String>) {
|
|||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun ResetKeyPasswordViewPreview() {
|
||||
internal fun ResetIdentityPasswordViewPreview() {
|
||||
ElementPreview {
|
||||
ResetKeyPasswordView(
|
||||
state = ResetKeyPasswordState(
|
||||
ResetIdentityPasswordView(
|
||||
state = ResetIdentityPasswordState(
|
||||
resetAction = AsyncAction.Uninitialized,
|
||||
eventSink = {}
|
||||
),
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package io.element.android.features.securebackup.impl.reset.root
|
||||
|
||||
sealed interface ResetKeyRootEvent {
|
||||
data object Continue : ResetKeyRootEvent
|
||||
data object DismissDialog : ResetKeyRootEvent
|
||||
sealed interface ResetIdentityRootEvent {
|
||||
data object Continue : ResetIdentityRootEvent
|
||||
data object DismissDialog : ResetIdentityRootEvent
|
||||
}
|
||||
|
|
@ -27,7 +27,7 @@ import io.element.android.anvilannotations.ContributesNode
|
|||
import io.element.android.libraries.di.SessionScope
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
class ResetKeyRootNode @AssistedInject constructor(
|
||||
class ResetIdentityRootNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
|
|
@ -35,13 +35,13 @@ class ResetKeyRootNode @AssistedInject constructor(
|
|||
fun onContinue()
|
||||
}
|
||||
|
||||
private val presenter = ResetKeyRootPresenter()
|
||||
private val presenter = ResetIdentityRootPresenter()
|
||||
private val callback: Callback = plugins.filterIsInstance<Callback>().first()
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
ResetKeyRootView(
|
||||
ResetIdentityRootView(
|
||||
state = state,
|
||||
onContinue = callback::onContinue,
|
||||
onBack = ::navigateUp,
|
||||
|
|
@ -23,19 +23,19 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.setValue
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
|
||||
class ResetKeyRootPresenter : Presenter<ResetKeyRootState> {
|
||||
class ResetIdentityRootPresenter : Presenter<ResetIdentityRootState> {
|
||||
@Composable
|
||||
override fun present(): ResetKeyRootState {
|
||||
override fun present(): ResetIdentityRootState {
|
||||
var displayConfirmDialog by remember { mutableStateOf(false) }
|
||||
|
||||
fun handleEvent(event: ResetKeyRootEvent) {
|
||||
fun handleEvent(event: ResetIdentityRootEvent) {
|
||||
displayConfirmDialog = when (event) {
|
||||
ResetKeyRootEvent.Continue -> true
|
||||
ResetKeyRootEvent.DismissDialog -> false
|
||||
ResetIdentityRootEvent.Continue -> true
|
||||
ResetIdentityRootEvent.DismissDialog -> false
|
||||
}
|
||||
}
|
||||
|
||||
return ResetKeyRootState(
|
||||
return ResetIdentityRootState(
|
||||
displayConfirmationDialog = displayConfirmDialog,
|
||||
eventSink = ::handleEvent
|
||||
)
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package io.element.android.features.securebackup.impl.reset.root
|
||||
|
||||
data class ResetKeyRootState(
|
||||
data class ResetIdentityRootState(
|
||||
val displayConfirmationDialog: Boolean,
|
||||
val eventSink: (ResetKeyRootEvent) -> Unit,
|
||||
val eventSink: (ResetIdentityRootEvent) -> Unit,
|
||||
)
|
||||
|
|
@ -18,14 +18,14 @@ package io.element.android.features.securebackup.impl.reset.root
|
|||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
|
||||
class ResetKeyRootStateProvider : PreviewParameterProvider<ResetKeyRootState> {
|
||||
override val values: Sequence<ResetKeyRootState>
|
||||
class ResetIdentityRootStateProvider : PreviewParameterProvider<ResetIdentityRootState> {
|
||||
override val values: Sequence<ResetIdentityRootState>
|
||||
get() = sequenceOf(
|
||||
ResetKeyRootState(
|
||||
ResetIdentityRootState(
|
||||
displayConfirmationDialog = false,
|
||||
eventSink = {}
|
||||
),
|
||||
ResetKeyRootState(
|
||||
ResetIdentityRootState(
|
||||
displayConfirmationDialog = true,
|
||||
eventSink = {}
|
||||
)
|
||||
|
|
@ -43,8 +43,8 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
|||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Composable
|
||||
fun ResetKeyRootView(
|
||||
state: ResetKeyRootState,
|
||||
fun ResetIdentityRootView(
|
||||
state: ResetIdentityRootState,
|
||||
onContinue: () -> Unit,
|
||||
onBack: () -> Unit,
|
||||
) {
|
||||
|
|
@ -58,7 +58,7 @@ fun ResetKeyRootView(
|
|||
Button(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(id = CommonStrings.action_continue),
|
||||
onClick = { state.eventSink(ResetKeyRootEvent.Continue) },
|
||||
onClick = { state.eventSink(ResetIdentityRootEvent.Continue) },
|
||||
destructive = true,
|
||||
)
|
||||
},
|
||||
|
|
@ -71,11 +71,11 @@ fun ResetKeyRootView(
|
|||
content = stringResource(CommonStrings.screen_reset_encryption_confirmation_alert_subtitle),
|
||||
submitText = stringResource(CommonStrings.screen_reset_encryption_confirmation_alert_action),
|
||||
onSubmitClick = {
|
||||
state.eventSink(ResetKeyRootEvent.DismissDialog)
|
||||
state.eventSink(ResetIdentityRootEvent.DismissDialog)
|
||||
onContinue()
|
||||
},
|
||||
destructiveSubmit = true,
|
||||
onDismiss = { state.eventSink(ResetKeyRootEvent.DismissDialog) }
|
||||
onDismiss = { state.eventSink(ResetIdentityRootEvent.DismissDialog) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -138,9 +138,9 @@ private fun Content() {
|
|||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun ResetKeyRootViewPreview(@PreviewParameter(ResetKeyRootStateProvider::class) state: ResetKeyRootState) {
|
||||
internal fun ResetIdentityRootViewPreview(@PreviewParameter(ResetIdentityRootStateProvider::class) state: ResetIdentityRootState) {
|
||||
ElementPreview {
|
||||
ResetKeyRootView(
|
||||
ResetIdentityRootView(
|
||||
state = state,
|
||||
onContinue = {},
|
||||
onBack = {},
|
||||
Loading…
Add table
Add a link
Reference in a new issue