[Room Details] Leave room (#296)
* Add leave room functionality to the Room Details screen * Add snackbar message throught `SnackbarDistpacher`
This commit is contained in:
parent
5fdf16772f
commit
9064481b4c
32 changed files with 564 additions and 71 deletions
|
|
@ -18,6 +18,7 @@ package io.element.android.features.roomdetails.impl
|
|||
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
|
|
@ -26,7 +27,7 @@ import javax.inject.Inject
|
|||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultRoomDetailsEntryPoint @Inject constructor() : RoomDetailsEntryPoint {
|
||||
override fun createNode(parentNode: Node, buildContext: BuildContext): Node {
|
||||
return parentNode.createNode<RoomDetailsFlowNode>(buildContext)
|
||||
override fun createNode(parentNode: Node, buildContext: BuildContext, plugins: List<Plugin>): Node {
|
||||
return parentNode.createNode<RoomDetailsFlowNode>(buildContext, plugins)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,4 +16,8 @@
|
|||
|
||||
package io.element.android.features.roomdetails.impl
|
||||
|
||||
sealed interface RoomDetailsEvent
|
||||
sealed interface RoomDetailsEvent {
|
||||
data class LeaveRoom(val needsConfirmation: Boolean) : RoomDetailsEvent
|
||||
object ClearLeaveRoomWarning : RoomDetailsEvent
|
||||
object ClearError : RoomDetailsEvent
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,22 +21,31 @@ import androidx.compose.runtime.LaunchedEffect
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
class RoomDetailsPresenter @Inject constructor(
|
||||
private val room: MatrixRoom,
|
||||
private val roomMembershipObserver: RoomMembershipObserver,
|
||||
) : Presenter<RoomDetailsState> {
|
||||
|
||||
@Composable
|
||||
override fun present(): RoomDetailsState {
|
||||
// fun handleEvents(event: RoomDetailsEvent) {}
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var leaveRoomWarning by remember {
|
||||
mutableStateOf<LeaveRoomWarning?>(null)
|
||||
}
|
||||
var error by remember {
|
||||
mutableStateOf<RoomDetailsError?>(null)
|
||||
}
|
||||
var memberCount: Async<Int> by remember { mutableStateOf(Async.Loading()) }
|
||||
LaunchedEffect(Unit) {
|
||||
withContext(Dispatchers.IO) {
|
||||
|
|
@ -47,6 +56,28 @@ class RoomDetailsPresenter @Inject constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
fun handleEvents(event: RoomDetailsEvent) {
|
||||
when (event) {
|
||||
is RoomDetailsEvent.LeaveRoom -> {
|
||||
if (event.needsConfirmation) {
|
||||
leaveRoomWarning = LeaveRoomWarning.computeLeaveRoomWarning(room.isPublic, memberCount)
|
||||
} else {
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
room.leave()
|
||||
.onSuccess {
|
||||
roomMembershipObserver.notifyUserLeftRoom(room.roomId)
|
||||
}.onFailure {
|
||||
error = RoomDetailsError.AlertGeneric
|
||||
}
|
||||
leaveRoomWarning = null
|
||||
}
|
||||
}
|
||||
}
|
||||
is RoomDetailsEvent.ClearLeaveRoomWarning -> leaveRoomWarning = null
|
||||
RoomDetailsEvent.ClearError -> error = null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return RoomDetailsState(
|
||||
roomId = room.roomId.value,
|
||||
|
|
@ -56,7 +87,9 @@ class RoomDetailsPresenter @Inject constructor(
|
|||
roomTopic = room.topic,
|
||||
memberCount = memberCount,
|
||||
isEncrypted = room.isEncrypted,
|
||||
// eventSink = ::handleEvents
|
||||
displayLeaveRoomWarning = leaveRoomWarning,
|
||||
error = error,
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@
|
|||
package io.element.android.features.roomdetails.impl
|
||||
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.architecture.isLoading
|
||||
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
|
||||
data class RoomDetailsState(
|
||||
val roomId: String,
|
||||
|
|
@ -26,5 +29,27 @@ data class RoomDetailsState(
|
|||
val roomTopic: String?,
|
||||
val memberCount: Async<Int>,
|
||||
val isEncrypted: Boolean,
|
||||
// val eventSink: (RoomDetailsEvent) -> Unit
|
||||
val displayLeaveRoomWarning: LeaveRoomWarning?,
|
||||
val error: RoomDetailsError?,
|
||||
val eventSink: (RoomDetailsEvent) -> Unit
|
||||
)
|
||||
|
||||
sealed class LeaveRoomWarning {
|
||||
object Generic : LeaveRoomWarning()
|
||||
object PrivateRoom : LeaveRoomWarning()
|
||||
object LastUserInRoom : LeaveRoomWarning()
|
||||
|
||||
companion object {
|
||||
fun computeLeaveRoomWarning(isPublic: Boolean, memberCount: Async<Int>): LeaveRoomWarning {
|
||||
return when {
|
||||
!isPublic -> PrivateRoom
|
||||
(memberCount as? Async.Success<Int>)?.state == 1 -> LastUserInRoom
|
||||
else -> Generic
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface RoomDetailsError {
|
||||
object AlertGeneric : RoomDetailsError
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,5 +43,7 @@ fun aRoomDetailsState() = RoomDetailsState(
|
|||
"|| MAI iki/Marketing...",
|
||||
memberCount = Async.Success(32),
|
||||
isEncrypted = true,
|
||||
// eventSink = {}
|
||||
displayLeaveRoomWarning = null,
|
||||
error = null,
|
||||
eventSink = {}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ import io.element.android.libraries.designsystem.components.avatar.Avatar
|
|||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
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.components.preferences.PreferenceCategory
|
||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceText
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||
|
|
@ -57,6 +59,7 @@ import io.element.android.libraries.designsystem.theme.LocalColors
|
|||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.ui.strings.R as StringR
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
|
|
@ -101,7 +104,24 @@ fun RoomDetailsView(
|
|||
SecuritySection()
|
||||
}
|
||||
|
||||
OtherActionsSection()
|
||||
OtherActionsSection(onLeaveRoom = {
|
||||
state.eventSink(RoomDetailsEvent.LeaveRoom(needsConfirmation = true))
|
||||
})
|
||||
|
||||
if (state.displayLeaveRoomWarning != null) {
|
||||
ConfirmLeaveRoomDialog(
|
||||
leaveRoomWarning = state.displayLeaveRoomWarning,
|
||||
onConfirmLeave = { state.eventSink(RoomDetailsEvent.LeaveRoom(needsConfirmation = false)) },
|
||||
onDismiss = { state.eventSink(RoomDetailsEvent.ClearLeaveRoomWarning) }
|
||||
)
|
||||
}
|
||||
|
||||
if (state.error != null) {
|
||||
ErrorDialog(
|
||||
content = stringResource(StringR.string.error_unknown),
|
||||
onDismiss = { state.eventSink(RoomDetailsEvent.ClearError) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -189,16 +209,38 @@ internal fun SecuritySection(modifier: Modifier = Modifier) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
internal fun OtherActionsSection(modifier: Modifier = Modifier) {
|
||||
internal fun OtherActionsSection(onLeaveRoom: () -> Unit, modifier: Modifier = Modifier) {
|
||||
PreferenceCategory(showDivider = false, modifier = modifier) {
|
||||
PreferenceText(
|
||||
title = stringResource(R.string.screen_room_details_leave_room_title),
|
||||
icon = ImageVector.vectorResource(R.drawable.ic_door_open),
|
||||
tintColor = LocalColors.current.textActionCritical,
|
||||
onClick = onLeaveRoom,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun ConfirmLeaveRoomDialog(
|
||||
leaveRoomWarning: LeaveRoomWarning,
|
||||
onConfirmLeave: () -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
val content = stringResource(
|
||||
when (leaveRoomWarning) {
|
||||
LeaveRoomWarning.PrivateRoom -> StringR.string.leave_room_alert_private_subtitle
|
||||
LeaveRoomWarning.LastUserInRoom -> StringR.string.leave_room_alert_empty_subtitle
|
||||
LeaveRoomWarning.Generic -> StringR.string.leave_room_alert_subtitle
|
||||
}
|
||||
)
|
||||
ConfirmationDialog(
|
||||
content = content,
|
||||
submitText = stringResource(StringR.string.action_leave),
|
||||
onSubmitClicked = onConfirmLeave,
|
||||
onDismiss = onDismiss,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun RoomDetailsLightPreview(@PreviewParameter(RoomDetailsStateProvider::class) state: RoomDetailsState) =
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue