feat(security&privacy) : manage save action for edit room address
This commit is contained in:
parent
70f39c4f7c
commit
873b6558cc
7 changed files with 101 additions and 10 deletions
|
|
@ -9,10 +9,12 @@ package io.element.android.features.roomdetails.impl.securityandprivacy
|
||||||
|
|
||||||
import com.bumble.appyx.core.plugin.Plugin
|
import com.bumble.appyx.core.plugin.Plugin
|
||||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||||
|
import com.bumble.appyx.navmodel.backstack.operation.pop
|
||||||
import com.bumble.appyx.navmodel.backstack.operation.push
|
import com.bumble.appyx.navmodel.backstack.operation.push
|
||||||
|
|
||||||
interface SecurityAndPrivacyNavigator : Plugin {
|
interface SecurityAndPrivacyNavigator : Plugin {
|
||||||
fun openEditRoomAddress()
|
fun openEditRoomAddress()
|
||||||
|
fun closeEditorRoomAddress()
|
||||||
}
|
}
|
||||||
|
|
||||||
class BackstackSecurityAndPrivacyNavigator(
|
class BackstackSecurityAndPrivacyNavigator(
|
||||||
|
|
@ -21,4 +23,8 @@ class BackstackSecurityAndPrivacyNavigator(
|
||||||
override fun openEditRoomAddress() {
|
override fun openEditRoomAddress() {
|
||||||
backStack.push(SecurityAndPrivacyFlowNode.NavTarget.EditRoomAddress)
|
backStack.push(SecurityAndPrivacyFlowNode.NavTarget.EditRoomAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun closeEditorRoomAddress() {
|
||||||
|
backStack.pop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,5 +9,6 @@ package io.element.android.features.roomdetails.impl.securityandprivacy.editroom
|
||||||
|
|
||||||
sealed interface EditRoomAddressEvents {
|
sealed interface EditRoomAddressEvents {
|
||||||
data object Save : EditRoomAddressEvents
|
data object Save : EditRoomAddressEvents
|
||||||
|
data object DismissError : EditRoomAddressEvents
|
||||||
data class RoomAddressChanged(val roomAddress: String) : EditRoomAddressEvents
|
data class RoomAddressChanged(val roomAddress: String) : EditRoomAddressEvents
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,18 +12,23 @@ import androidx.compose.ui.Modifier
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
import com.bumble.appyx.core.node.Node
|
import com.bumble.appyx.core.node.Node
|
||||||
import com.bumble.appyx.core.plugin.Plugin
|
import com.bumble.appyx.core.plugin.Plugin
|
||||||
|
import com.bumble.appyx.core.plugin.plugins
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
|
import io.element.android.features.roomdetails.impl.securityandprivacy.SecurityAndPrivacyNavigator
|
||||||
import io.element.android.libraries.di.RoomScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
|
|
||||||
@ContributesNode(RoomScope::class)
|
@ContributesNode(RoomScope::class)
|
||||||
class EditRoomAddressNode @AssistedInject constructor(
|
class EditRoomAddressNode @AssistedInject constructor(
|
||||||
@Assisted buildContext: BuildContext,
|
@Assisted buildContext: BuildContext,
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
private val presenter: EditRoomAddressPresenter,
|
presenterFactory: EditRoomAddressPresenter.Factory,
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins) {
|
||||||
|
|
||||||
|
private val navigator = plugins<SecurityAndPrivacyNavigator>().first()
|
||||||
|
private val presenter = presenterFactory.create(navigator)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
val state = presenter.present()
|
val state = presenter.present()
|
||||||
|
|
|
||||||
|
|
@ -8,27 +8,44 @@
|
||||||
package io.element.android.features.roomdetails.impl.securityandprivacy.editroomaddress
|
package io.element.android.features.roomdetails.impl.securityandprivacy.editroomaddress
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import io.element.android.features.roomdetails.impl.securityandprivacy.SecurityAndPrivacyNavigator
|
||||||
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper
|
import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper
|
||||||
|
import io.element.android.libraries.matrix.api.roomAliasFromName
|
||||||
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity
|
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity
|
||||||
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidityEffect
|
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidityEffect
|
||||||
import javax.inject.Inject
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
class EditRoomAddressPresenter @Inject constructor(
|
class EditRoomAddressPresenter @AssistedInject constructor(
|
||||||
|
@Assisted private val navigator: SecurityAndPrivacyNavigator,
|
||||||
private val client: MatrixClient,
|
private val client: MatrixClient,
|
||||||
private val room: MatrixRoom,
|
private val room: MatrixRoom,
|
||||||
private val roomAliasHelper: RoomAliasHelper,
|
private val roomAliasHelper: RoomAliasHelper,
|
||||||
) : Presenter<EditRoomAddressState> {
|
) : Presenter<EditRoomAddressState> {
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory {
|
||||||
|
fun create(navigator: SecurityAndPrivacyNavigator): EditRoomAddressPresenter
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): EditRoomAddressState {
|
override fun present(): EditRoomAddressState {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val homeserverName = remember { client.userIdServerName() }
|
val homeserverName = remember { client.userIdServerName() }
|
||||||
val roomAddressValidity = remember {
|
val roomAddressValidity = remember {
|
||||||
mutableStateOf<RoomAddressValidity>(RoomAddressValidity.Unknown)
|
mutableStateOf<RoomAddressValidity>(RoomAddressValidity.Unknown)
|
||||||
|
|
@ -36,7 +53,8 @@ class EditRoomAddressPresenter @Inject constructor(
|
||||||
val savedRoomAddress = remember {
|
val savedRoomAddress = remember {
|
||||||
room.firstAliasMatching(homeserverName)?.roomAddress()
|
room.firstAliasMatching(homeserverName)?.roomAddress()
|
||||||
}
|
}
|
||||||
var currentRoomAddress by remember {
|
val saveAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
|
||||||
|
var newRoomAddress by remember {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
savedRoomAddress ?: roomAliasHelper.roomAliasNameFromRoomDisplayName(room.displayName)
|
savedRoomAddress ?: roomAliasHelper.roomAliasNameFromRoomDisplayName(room.displayName)
|
||||||
)
|
)
|
||||||
|
|
@ -44,9 +62,16 @@ class EditRoomAddressPresenter @Inject constructor(
|
||||||
|
|
||||||
fun handleEvents(event: EditRoomAddressEvents) {
|
fun handleEvents(event: EditRoomAddressEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
EditRoomAddressEvents.Save -> Unit
|
EditRoomAddressEvents.Save -> coroutineScope.save(
|
||||||
|
saveAction = saveAction,
|
||||||
|
serverName = homeserverName,
|
||||||
|
newRoomAddress = newRoomAddress
|
||||||
|
)
|
||||||
is EditRoomAddressEvents.RoomAddressChanged -> {
|
is EditRoomAddressEvents.RoomAddressChanged -> {
|
||||||
currentRoomAddress = event.roomAddress
|
newRoomAddress = event.roomAddress
|
||||||
|
}
|
||||||
|
EditRoomAddressEvents.DismissError -> {
|
||||||
|
saveAction.value = AsyncAction.Uninitialized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -54,7 +79,7 @@ class EditRoomAddressPresenter @Inject constructor(
|
||||||
RoomAddressValidityEffect(
|
RoomAddressValidityEffect(
|
||||||
client = client,
|
client = client,
|
||||||
roomAliasHelper = roomAliasHelper,
|
roomAliasHelper = roomAliasHelper,
|
||||||
newRoomAddress = currentRoomAddress,
|
newRoomAddress = newRoomAddress,
|
||||||
knownRoomAddress = savedRoomAddress
|
knownRoomAddress = savedRoomAddress
|
||||||
) { newRoomAddressValidity ->
|
) { newRoomAddressValidity ->
|
||||||
roomAddressValidity.value = newRoomAddressValidity
|
roomAddressValidity.value = newRoomAddressValidity
|
||||||
|
|
@ -63,11 +88,43 @@ class EditRoomAddressPresenter @Inject constructor(
|
||||||
return EditRoomAddressState(
|
return EditRoomAddressState(
|
||||||
homeserverName = homeserverName,
|
homeserverName = homeserverName,
|
||||||
roomAddressValidity = roomAddressValidity.value,
|
roomAddressValidity = roomAddressValidity.value,
|
||||||
roomAddress = currentRoomAddress,
|
roomAddress = newRoomAddress,
|
||||||
|
saveAction = saveAction.value,
|
||||||
eventSink = ::handleEvents
|
eventSink = ::handleEvents
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun CoroutineScope.save(
|
||||||
|
saveAction: MutableState<AsyncAction<Unit>>,
|
||||||
|
serverName: String,
|
||||||
|
newRoomAddress: String,
|
||||||
|
) = launch {
|
||||||
|
suspend {
|
||||||
|
val savedCanonicalAlias = room.canonicalAlias
|
||||||
|
val savedAliasFromHomeserver = room.firstAliasMatching(serverName)
|
||||||
|
val newRoomAlias = client.roomAliasFromName(newRoomAddress).getOrThrow()
|
||||||
|
|
||||||
|
// First publish the new alias in the room directory
|
||||||
|
room.publishRoomAliasInRoomDirectory(newRoomAlias).getOrThrow()
|
||||||
|
// Then try remove the old alias from the room directory
|
||||||
|
if (savedAliasFromHomeserver != null) {
|
||||||
|
room.removeRoomAliasFromRoomDirectory(savedAliasFromHomeserver).getOrThrow()
|
||||||
|
}
|
||||||
|
// Finally update the canonical alias state..
|
||||||
|
when {
|
||||||
|
// Allow to update the canonical alias only if the saved canonical alias matches the homeserver or if there is no canonical alias
|
||||||
|
savedCanonicalAlias == null || savedCanonicalAlias.matchesServer(serverName) -> {
|
||||||
|
room.updateCanonicalAlias(newRoomAlias, room.alternativeAliases).getOrThrow()
|
||||||
|
}
|
||||||
|
// Otherwise, update the alternative aliases and keep the current canonical alias
|
||||||
|
else -> {
|
||||||
|
val newAlternativeAliases = listOf(newRoomAlias) + room.alternativeAliases
|
||||||
|
room.updateCanonicalAlias(savedCanonicalAlias, newAlternativeAliases).getOrThrow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
navigator.closeEditorRoomAddress()
|
||||||
|
}.runCatchingUpdatingState(saveAction)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun MatrixRoom.firstAliasMatching(serverName: String): RoomAlias? {
|
private fun MatrixRoom.firstAliasMatching(serverName: String): RoomAlias? {
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,14 @@
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.securityandprivacy.editroomaddress
|
package io.element.android.features.roomdetails.impl.securityandprivacy.editroomaddress
|
||||||
|
|
||||||
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity
|
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity
|
||||||
|
|
||||||
data class EditRoomAddressState(
|
data class EditRoomAddressState(
|
||||||
val homeserverName: String,
|
val homeserverName: String,
|
||||||
val roomAddress: String,
|
val roomAddress: String,
|
||||||
val roomAddressValidity: RoomAddressValidity,
|
val roomAddressValidity: RoomAddressValidity,
|
||||||
|
val saveAction: AsyncAction<Unit>,
|
||||||
val eventSink: (EditRoomAddressEvents) -> Unit
|
val eventSink: (EditRoomAddressEvents) -> Unit
|
||||||
) {
|
) {
|
||||||
val canBeSaved = roomAddressValidity == RoomAddressValidity.Valid
|
val canBeSaved = roomAddressValidity == RoomAddressValidity.Valid
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,17 @@
|
||||||
package io.element.android.features.roomdetails.impl.securityandprivacy.editroomaddress
|
package io.element.android.features.roomdetails.impl.securityandprivacy.editroomaddress
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity
|
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity
|
||||||
|
|
||||||
open class EditRoomAddressStateProvider : PreviewParameterProvider<EditRoomAddressState> {
|
open class EditRoomAddressStateProvider : PreviewParameterProvider<EditRoomAddressState> {
|
||||||
override val values: Sequence<EditRoomAddressState>
|
override val values: Sequence<EditRoomAddressState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aEditRoomAddressState(),
|
aEditRoomAddressState(),
|
||||||
// Add other states here
|
aEditRoomAddressState(roomAddressValidity = RoomAddressValidity.NotAvailable),
|
||||||
|
aEditRoomAddressState(roomAddressValidity = RoomAddressValidity.InvalidSymbols),
|
||||||
|
aEditRoomAddressState(roomAddressValidity = RoomAddressValidity.Valid),
|
||||||
|
aEditRoomAddressState(roomAddressValidity = RoomAddressValidity.Valid, saveAction = AsyncAction.Loading),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -22,10 +26,12 @@ fun aEditRoomAddressState(
|
||||||
roomAddress: String = "therapy",
|
roomAddress: String = "therapy",
|
||||||
roomAddressValidity: RoomAddressValidity = RoomAddressValidity.Unknown,
|
roomAddressValidity: RoomAddressValidity = RoomAddressValidity.Unknown,
|
||||||
homeserverName: String = ":myserver.org",
|
homeserverName: String = ":myserver.org",
|
||||||
|
saveAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
eventSink: (EditRoomAddressEvents) -> Unit = {}
|
eventSink: (EditRoomAddressEvents) -> Unit = {}
|
||||||
) = EditRoomAddressState(
|
) = EditRoomAddressState(
|
||||||
roomAddress = roomAddress,
|
roomAddress = roomAddress,
|
||||||
roomAddressValidity = roomAddressValidity,
|
roomAddressValidity = roomAddressValidity,
|
||||||
homeserverName = homeserverName,
|
homeserverName = homeserverName,
|
||||||
|
saveAction = saveAction,
|
||||||
eventSink = eventSink
|
eventSink = eventSink
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
|
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||||
|
import io.element.android.libraries.designsystem.components.async.AsyncActionViewDefaults
|
||||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
|
@ -70,8 +72,20 @@ fun EditRoomAddressView(
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(all = 16.dp)
|
.padding(all = 16.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
AsyncActionView(
|
||||||
|
async = state.saveAction,
|
||||||
|
progressDialog = {
|
||||||
|
AsyncActionViewDefaults.ProgressDialog(
|
||||||
|
progressText = stringResource(CommonStrings.common_saving),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onSuccess = {},
|
||||||
|
errorMessage = { stringResource(CommonStrings.error_unknown) },
|
||||||
|
onRetry = { state.eventSink(EditRoomAddressEvents.Save) },
|
||||||
|
onErrorDismiss = { state.eventSink(EditRoomAddressEvents.DismissError) },
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue