Add ability to leave room via overflow menu

This commit is contained in:
Adam Brown 2023-01-07 14:26:31 +00:00
parent 3837dd29da
commit f1e05e0a6f
4 changed files with 88 additions and 4 deletions

View File

@ -67,7 +67,7 @@ internal fun MessengerScreen(
) { ) {
val state = viewModel.current val state = viewModel.current
viewModel.ObserveEvents(galleryLauncher) viewModel.ObserveEvents(galleryLauncher, navigator)
LifecycleEffect( LifecycleEffect(
onStart = { viewModel.dispatch(ComponentLifecycle.Visible) }, onStart = { viewModel.dispatch(ComponentLifecycle.Visible) },
onStop = { viewModel.dispatch(ComponentLifecycle.Gone) } onStop = { viewModel.dispatch(ComponentLifecycle.Gone) }
@ -85,6 +85,30 @@ internal fun MessengerScreen(
onImageClick = { viewModel.dispatch(ComposerStateChange.ImagePreview.Show(it)) } onImageClick = { viewModel.dispatch(ComposerStateChange.ImagePreview.Show(it)) }
) )
when (val dialog = state.dialogState) {
null -> {
// do nothing
}
is DialogState.PositiveNegative -> {
AlertDialog(
onDismissRequest = { viewModel.dispatch(ScreenAction.LeaveRoomConfirmation.Deny) },
confirmButton = {
Button(onClick = { viewModel.dispatch(ScreenAction.LeaveRoomConfirmation.Confirm) }) {
Text("Leave room")
}
},
dismissButton = {
Button(onClick = { viewModel.dispatch(ScreenAction.LeaveRoomConfirmation.Deny) }) {
Text("Cancel")
}
},
title = { Text(dialog.title) },
text = { Text(dialog.subtitle) }
)
}
}
Column { Column {
Toolbar(onNavigate = { navigator.navigate.upToHome() }, roomTitle, actions = { Toolbar(onNavigate = { navigator.navigate.upToHome() }, roomTitle, actions = {
state.roomState.takeIfContent()?.let { state.roomState.takeIfContent()?.let {
@ -98,8 +122,10 @@ internal fun MessengerScreen(
viewModel.dispatch(ScreenAction.Notifications.Mute) viewModel.dispatch(ScreenAction.Notifications.Mute)
}) })
} }
DropdownMenuItem(text = { Text("Leave room", color = MaterialTheme.colorScheme.onSecondaryContainer) }, onClick = {
viewModel.dispatch(ScreenAction.LeaveRoom)
})
} }
} }
}) })
@ -207,7 +233,7 @@ private fun ZoomableImage(viewerState: ViewerState) {
} }
@Composable @Composable
private fun MessengerState.ObserveEvents(galleryLauncher: ActivityResultLauncher<ImageGalleryActivityPayload>) { private fun MessengerState.ObserveEvents(galleryLauncher: ActivityResultLauncher<ImageGalleryActivityPayload>, navigator: Navigator) {
val context = LocalContext.current val context = LocalContext.current
StartObserving { StartObserving {
this@ObserveEvents.events.launch { this@ObserveEvents.events.launch {
@ -221,6 +247,8 @@ private fun MessengerState.ObserveEvents(galleryLauncher: ActivityResultLauncher
is MessengerEvent.Toast -> { is MessengerEvent.Toast -> {
Toast.makeText(context, it.message, Toast.LENGTH_SHORT).show() Toast.makeText(context, it.message, Toast.LENGTH_SHORT).show()
} }
MessengerEvent.OnLeftRoom -> navigator.navigate.upToHome()
} }
} }
} }

View File

@ -10,11 +10,19 @@ sealed interface ScreenAction : Action {
data class CopyToClipboard(val model: BubbleModel) : ScreenAction data class CopyToClipboard(val model: BubbleModel) : ScreenAction
object SendMessage : ScreenAction object SendMessage : ScreenAction
object OpenGalleryPicker : ScreenAction object OpenGalleryPicker : ScreenAction
object LeaveRoom : ScreenAction
sealed interface Notifications : ScreenAction { sealed interface Notifications : ScreenAction {
object Mute : Notifications object Mute : Notifications
object Unmute : Notifications object Unmute : Notifications
} }
sealed interface LeaveRoomConfirmation : ScreenAction {
object Confirm : LeaveRoomConfirmation
object Deny : LeaveRoomConfirmation
}
data class UpdateDialogState(val dialogState: DialogState?): ScreenAction
} }
sealed interface ComponentLifecycle : Action { sealed interface ComponentLifecycle : Action {

View File

@ -19,6 +19,7 @@ import app.dapk.st.navigator.MessageAttachment
import app.dapk.state.* import app.dapk.state.*
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlin.reflect.KClass
internal fun messengerReducer( internal fun messengerReducer(
jobBag: JobBag, jobBag: JobBag,
@ -36,6 +37,7 @@ internal fun messengerReducer(
roomState = Lce.Loading(), roomState = Lce.Loading(),
composerState = initialComposerState(initialAttachments), composerState = initialComposerState(initialAttachments),
viewerState = null, viewerState = null,
dialogState = null,
), ),
async(ComponentLifecycle::class) { action -> async(ComponentLifecycle::class) { action ->
@ -159,9 +161,43 @@ internal fun messengerReducer(
) )
) )
}, },
change(ScreenAction.UpdateDialogState::class) { action, state ->
state.copy(dialogState = action.dialogState)
},
rewrite(ScreenAction.LeaveRoom::class) {
ScreenAction.UpdateDialogState(
DialogState.PositiveNegative(
title = "Leave room",
subtitle = "Are you sure you want you leave the room? If the room is private you will need to be invited again to rejoin.",
negativeAction = ScreenAction.LeaveRoomConfirmation.Deny,
positiveAction = ScreenAction.LeaveRoomConfirmation.Confirm,
)
)
},
async(ScreenAction.LeaveRoomConfirmation::class) { action ->
dispatch(ScreenAction.UpdateDialogState(dialogState = null))
when (action) {
ScreenAction.LeaveRoomConfirmation.Confirm -> {
runCatching { chatEngine.rejectRoom(getState().roomId) }.fold(
onSuccess = { eventEmitter.invoke(MessengerEvent.OnLeftRoom) },
onFailure = { eventEmitter.invoke(MessengerEvent.Toast("Failed to leave room")) },
)
}
ScreenAction.LeaveRoomConfirmation.Deny -> {
// do nothing
}
}
},
) )
} }
private fun <A : Action, S> rewrite(klass: KClass<A>, mapper: (A) -> Action) = async<A, S>(klass) { action -> dispatch(mapper(action)) }
private suspend fun ChatEngine.sendTextMessage(content: MessengerPageState, composerState: ComposerState.Text) { private suspend fun ChatEngine.sendTextMessage(content: MessengerPageState, composerState: ComposerState.Text) {
val roomState = content.roomState val roomState = content.roomState
val message = SendMessage.TextMessage( val message = SendMessage.TextMessage(

View File

@ -1,12 +1,13 @@
package app.dapk.st.messenger.state package app.dapk.st.messenger.state
import app.dapk.st.core.Lce import app.dapk.st.core.Lce
import app.dapk.st.state.State
import app.dapk.st.design.components.BubbleModel import app.dapk.st.design.components.BubbleModel
import app.dapk.st.engine.MessengerPageState import app.dapk.st.engine.MessengerPageState
import app.dapk.st.engine.RoomEvent import app.dapk.st.engine.RoomEvent
import app.dapk.st.matrix.common.RoomId import app.dapk.st.matrix.common.RoomId
import app.dapk.st.navigator.MessageAttachment import app.dapk.st.navigator.MessageAttachment
import app.dapk.st.state.State
import app.dapk.state.Action
typealias MessengerState = State<MessengerScreenState, MessengerEvent> typealias MessengerState = State<MessengerScreenState, MessengerEvent>
@ -15,15 +16,26 @@ data class MessengerScreenState(
val roomState: Lce<MessengerPageState>, val roomState: Lce<MessengerPageState>,
val composerState: ComposerState, val composerState: ComposerState,
val viewerState: ViewerState?, val viewerState: ViewerState?,
val dialogState: DialogState?,
) )
data class ViewerState( data class ViewerState(
val event: BubbleModel.Image, val event: BubbleModel.Image,
) )
sealed interface DialogState {
data class PositiveNegative(
val title: String,
val subtitle: String,
val positiveAction: Action,
val negativeAction: Action,
) : DialogState
}
sealed interface MessengerEvent { sealed interface MessengerEvent {
object SelectImageAttachment : MessengerEvent object SelectImageAttachment : MessengerEvent
data class Toast(val message: String) : MessengerEvent data class Toast(val message: String) : MessengerEvent
object OnLeftRoom : MessengerEvent
} }
sealed interface ComposerState { sealed interface ComposerState {