From d44c331eab1f7ebcd87f8adda1f37e73858ff408 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 26 Jan 2023 21:30:46 +0000 Subject: [PATCH] Add app room state persistence and allow rooms to be marked with bubbles --- .../kotlin/app/dapk/st/graph/AppModule.kt | 1 + .../kotlin/app/dapk/st/domain/StoreModule.kt | 3 ++ .../domain/application/AppRoomPersistence.kt | 38 +++++++++++++++++++ .../app/dapk/db/app/migration/1.sqm | 5 +++ .../app/dapk/db/{ => app}/model/AppRoom.sq | 10 ++--- .../dapk/db/{ => app}/model/EventLogger.sq | 0 .../sqldelight/app/dapk/db/migration/1.sqm | 4 -- .../sqldelight/app/dapk/db/migration/2.sqm | 5 --- .../app/dapk/st/messenger/MessengerModule.kt | 3 ++ .../app/dapk/st/messenger/MessengerScreen.kt | 14 ++++--- .../st/messenger/state/MessengerAction.kt | 2 + .../st/messenger/state/MessengerReducer.kt | 23 +++++++++-- .../dapk/st/messenger/state/MessengerState.kt | 5 +-- .../dapk/st/messenger/MessengerReducerTest.kt | 2 + 14 files changed, 87 insertions(+), 28 deletions(-) create mode 100644 domains/store/src/main/kotlin/app/dapk/st/domain/application/AppRoomPersistence.kt create mode 100644 domains/store/src/main/sqldelight/app/dapk/db/app/migration/1.sqm rename domains/store/src/main/sqldelight/app/dapk/db/{ => app}/model/AppRoom.sq (54%) rename domains/store/src/main/sqldelight/app/dapk/db/{ => app}/model/EventLogger.sq (100%) delete mode 100644 domains/store/src/main/sqldelight/app/dapk/db/migration/1.sqm delete mode 100644 domains/store/src/main/sqldelight/app/dapk/db/migration/2.sqm diff --git a/app/src/main/kotlin/app/dapk/st/graph/AppModule.kt b/app/src/main/kotlin/app/dapk/st/graph/AppModule.kt index 1544fab..2fd4f31 100644 --- a/app/src/main/kotlin/app/dapk/st/graph/AppModule.kt +++ b/app/src/main/kotlin/app/dapk/st/graph/AppModule.kt @@ -180,6 +180,7 @@ internal class FeatureModules internal constructor( chatEngineModule.engine, context, storeModule.value.messageStore(), + storeModule.value.appRoomStore(), deviceMeta, ) } diff --git a/domains/store/src/main/kotlin/app/dapk/st/domain/StoreModule.kt b/domains/store/src/main/kotlin/app/dapk/st/domain/StoreModule.kt index 4cc6a4d..98a7436 100644 --- a/domains/store/src/main/kotlin/app/dapk/st/domain/StoreModule.kt +++ b/domains/store/src/main/kotlin/app/dapk/st/domain/StoreModule.kt @@ -3,6 +3,7 @@ package app.dapk.st.domain import app.dapk.db.app.StDb import app.dapk.st.core.CoroutineDispatchers import app.dapk.st.core.Preferences +import app.dapk.st.domain.application.AppRoomPersistence import app.dapk.st.domain.application.eventlog.EventLogPersistence import app.dapk.st.domain.application.eventlog.LoggingStore import app.dapk.st.domain.application.message.MessageOptionsStore @@ -41,4 +42,6 @@ class StoreModule( fun messageStore(): MessageOptionsStore = MessageOptionsStore(cachingPreferences) + fun appRoomStore(): AppRoomPersistence = AppRoomPersistence(database, coroutineDispatchers) + } diff --git a/domains/store/src/main/kotlin/app/dapk/st/domain/application/AppRoomPersistence.kt b/domains/store/src/main/kotlin/app/dapk/st/domain/application/AppRoomPersistence.kt new file mode 100644 index 0000000..a474064 --- /dev/null +++ b/domains/store/src/main/kotlin/app/dapk/st/domain/application/AppRoomPersistence.kt @@ -0,0 +1,38 @@ +package app.dapk.st.domain.application + +import app.dapk.db.app.StDb +import app.dapk.st.core.CoroutineDispatchers +import app.dapk.st.core.withIoContext +import app.dapk.st.matrix.common.RoomId +import com.squareup.sqldelight.runtime.coroutines.asFlow +import com.squareup.sqldelight.runtime.coroutines.mapToOneOrNull +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +private val DEFAULT_STATE = AppRoomState(isChatBubble = false) + +class AppRoomPersistence( + private val database: StDb, + private val coroutineDispatchers: CoroutineDispatchers, +) { + + fun observe(roomId: RoomId): Flow { + return database.appRoomQueries.select(roomId.value) + .asFlow() + .mapToOneOrNull(coroutineDispatchers.io) + .map { it?.let { AppRoomState(isChatBubble = it.is_bubble == 1) } ?: DEFAULT_STATE } + } + + suspend fun markBubble(roomId: RoomId) = coroutineDispatchers.withIoContext { + database.appRoomQueries.updateBubble(roomId.value, is_bubble = 1) + } + + suspend fun unmarkBubble(roomId: RoomId) = coroutineDispatchers.withIoContext { + database.appRoomQueries.updateBubble(roomId.value, is_bubble = 0) + } +} + + +data class AppRoomState( + val isChatBubble: Boolean +) \ No newline at end of file diff --git a/domains/store/src/main/sqldelight/app/dapk/db/app/migration/1.sqm b/domains/store/src/main/sqldelight/app/dapk/db/app/migration/1.sqm new file mode 100644 index 0000000..7b128b3 --- /dev/null +++ b/domains/store/src/main/sqldelight/app/dapk/db/app/migration/1.sqm @@ -0,0 +1,5 @@ +CREATE TABLE IF NOT EXISTS dbAppRoom ( + room_id TEXT NOT NULL, + is_bubble INTEGER AS Int NOT NULL, + PRIMARY KEY (room_id) +); \ No newline at end of file diff --git a/domains/store/src/main/sqldelight/app/dapk/db/model/AppRoom.sq b/domains/store/src/main/sqldelight/app/dapk/db/app/model/AppRoom.sq similarity index 54% rename from domains/store/src/main/sqldelight/app/dapk/db/model/AppRoom.sq rename to domains/store/src/main/sqldelight/app/dapk/db/app/model/AppRoom.sq index 0ace052..6a07370 100644 --- a/domains/store/src/main/sqldelight/app/dapk/db/model/AppRoom.sq +++ b/domains/store/src/main/sqldelight/app/dapk/db/app/model/AppRoom.sq @@ -1,18 +1,14 @@ CREATE TABLE dbAppRoom ( room_id TEXT NOT NULL, - is_bubble INTEGER AS Int NOT NULL DEFAULT 0, + is_bubble INTEGER AS Int NOT NULL, PRIMARY KEY (room_id) ); -markBubble: -INSERT OR REPLACE INTO dbAppRoom(room_id, is_bubble) -VALUES (?,?); - -unmarkBubble: +updateBubble: INSERT OR REPLACE INTO dbAppRoom(room_id, is_bubble) VALUES (?,?); select: -SELECT is_bubble +SELECT * FROM dbAppRoom WHERE room_id = ?; diff --git a/domains/store/src/main/sqldelight/app/dapk/db/model/EventLogger.sq b/domains/store/src/main/sqldelight/app/dapk/db/app/model/EventLogger.sq similarity index 100% rename from domains/store/src/main/sqldelight/app/dapk/db/model/EventLogger.sq rename to domains/store/src/main/sqldelight/app/dapk/db/app/model/EventLogger.sq diff --git a/domains/store/src/main/sqldelight/app/dapk/db/migration/1.sqm b/domains/store/src/main/sqldelight/app/dapk/db/migration/1.sqm deleted file mode 100644 index 4eb2ca8..0000000 --- a/domains/store/src/main/sqldelight/app/dapk/db/migration/1.sqm +++ /dev/null @@ -1,4 +0,0 @@ -CREATE TABLE IF NOT EXISTS dbMutedRoom ( - room_id TEXT NOT NULL, - PRIMARY KEY (room_id) -); diff --git a/domains/store/src/main/sqldelight/app/dapk/db/migration/2.sqm b/domains/store/src/main/sqldelight/app/dapk/db/migration/2.sqm deleted file mode 100644 index f4ee11d..0000000 --- a/domains/store/src/main/sqldelight/app/dapk/db/migration/2.sqm +++ /dev/null @@ -1,5 +0,0 @@ -CREATE TABLE IF NOT EXISTS dbStRoomMeta ( - room_id TEXT NOT NULL, - is_bubble INTEGER AS Int NOT NULL DEFAULT 0, - PRIMARY KEY (room_id) -); \ No newline at end of file diff --git a/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerModule.kt b/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerModule.kt index 13ea4fa..f311e64 100644 --- a/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerModule.kt +++ b/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerModule.kt @@ -5,6 +5,7 @@ import android.content.Context import app.dapk.st.core.DeviceMeta import app.dapk.st.core.JobBag import app.dapk.st.core.ProvidableModule +import app.dapk.st.domain.application.AppRoomPersistence import app.dapk.st.state.createStateViewModel import app.dapk.st.domain.application.message.MessageOptionsStore import app.dapk.st.engine.ChatEngine @@ -16,6 +17,7 @@ class MessengerModule( private val chatEngine: ChatEngine, private val context: Context, private val messageOptionsStore: MessageOptionsStore, + private val appRoomStore: AppRoomPersistence, val deviceMeta: DeviceMeta, ) : ProvidableModule { @@ -29,6 +31,7 @@ class MessengerModule( messageOptionsStore, RoomId(launchPayload.roomId), launchPayload.attachments, + appRoomStore, it ) } diff --git a/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerScreen.kt b/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerScreen.kt index 375f47c..892b33a 100644 --- a/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerScreen.kt +++ b/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerScreen.kt @@ -122,13 +122,17 @@ internal fun MessengerScreen( } viewModel.dispatch(action) } - BooleanOption(value = it.isMuted, trueText = "Disable bubble", falseText = "Enable bubble") { - val action = when (it) { - true -> ChatBubble.Disable - false -> ChatBubble.Enable + + state.appRoomState.takeIfContent()?.let { + BooleanOption(value = it.isChatBubble, trueText = "Disable bubble", falseText = "Enable bubble") { + val action = when (it) { + true -> ChatBubble.Disable + false -> ChatBubble.Enable + } + viewModel.dispatch(action) } - viewModel.dispatch(action) } + DropdownMenuItem(text = { Text("Leave room", color = MaterialTheme.colorScheme.onSecondaryContainer) }, onClick = { viewModel.dispatch(ScreenAction.LeaveRoom) }) diff --git a/features/messenger/src/main/kotlin/app/dapk/st/messenger/state/MessengerAction.kt b/features/messenger/src/main/kotlin/app/dapk/st/messenger/state/MessengerAction.kt index fdea223..628af04 100644 --- a/features/messenger/src/main/kotlin/app/dapk/st/messenger/state/MessengerAction.kt +++ b/features/messenger/src/main/kotlin/app/dapk/st/messenger/state/MessengerAction.kt @@ -1,6 +1,7 @@ package app.dapk.st.messenger.state import app.dapk.st.design.components.BubbleModel +import app.dapk.st.domain.application.AppRoomState import app.dapk.st.engine.MessengerPageState import app.dapk.st.engine.RoomEvent import app.dapk.st.navigator.MessageAttachment @@ -37,6 +38,7 @@ sealed interface ComponentLifecycle : Action { sealed interface MessagesStateChange : Action { data class Content(val content: MessengerPageState) : MessagesStateChange + data class AppRoomContent(val content: AppRoomState) : MessagesStateChange data class MuteContent(val isMuted: Boolean) : MessagesStateChange data class ChatBubbleContent(val isChatBubble: Boolean) : MessagesStateChange } diff --git a/features/messenger/src/main/kotlin/app/dapk/st/messenger/state/MessengerReducer.kt b/features/messenger/src/main/kotlin/app/dapk/st/messenger/state/MessengerReducer.kt index c48a82d..eb5d5d8 100644 --- a/features/messenger/src/main/kotlin/app/dapk/st/messenger/state/MessengerReducer.kt +++ b/features/messenger/src/main/kotlin/app/dapk/st/messenger/state/MessengerReducer.kt @@ -1,12 +1,14 @@ package app.dapk.st.messenger.state import android.os.Build +import android.util.Log import app.dapk.st.core.DeviceMeta import app.dapk.st.core.JobBag import app.dapk.st.core.Lce import app.dapk.st.core.asString import app.dapk.st.core.extensions.takeIfContent import app.dapk.st.design.components.BubbleModel +import app.dapk.st.domain.application.AppRoomPersistence import app.dapk.st.domain.application.message.MessageOptionsStore import app.dapk.st.engine.ChatEngine import app.dapk.st.engine.MessengerPageState @@ -29,6 +31,7 @@ internal fun messengerReducer( messageOptionsStore: MessageOptionsStore, roomId: RoomId, initialAttachments: List?, + appRoomStore: AppRoomPersistence, eventEmitter: suspend (MessengerEvent) -> Unit, ): ReducerFactory { return createReducer( @@ -45,6 +48,14 @@ internal fun messengerReducer( val state = getState() when (action) { is ComponentLifecycle.Visible -> { + jobBag.replace( + "appRoomState", + appRoomStore.observe(state.roomId) + .onEach { dispatch(MessagesStateChange.AppRoomContent(it)) } + .launchIn(coroutineScope) + ) + + jobBag.replace( "messages", chatEngine.messages(state.roomId, disableReadReceipts = messageOptionsStore.isReadReceiptsDisabled()) .onEach { dispatch(MessagesStateChange.Content(it)) } @@ -52,10 +63,14 @@ internal fun messengerReducer( ) } - ComponentLifecycle.Gone -> jobBag.cancel("messages") + ComponentLifecycle.Gone -> jobBag.cancelAll() } }, + change(MessagesStateChange.AppRoomContent::class) { action, state -> + state.copy(appRoomState = Lce.Content(action.content)) + }, + change(MessagesStateChange.Content::class) { action, state -> state.copy(roomState = Lce.Content(action.content)) }, @@ -174,9 +189,11 @@ internal fun messengerReducer( }, async(ScreenAction.ChatBubble::class) { action -> + Log.e("!!!", "$action") + val state = getState() when (action) { - ScreenAction.ChatBubble.Disable -> {} - ScreenAction.ChatBubble.Enable -> {} + ScreenAction.ChatBubble.Disable -> appRoomStore.unmarkBubble(state.roomId) + ScreenAction.ChatBubble.Enable -> appRoomStore.markBubble(state.roomId) } dispatch( diff --git a/features/messenger/src/main/kotlin/app/dapk/st/messenger/state/MessengerState.kt b/features/messenger/src/main/kotlin/app/dapk/st/messenger/state/MessengerState.kt index ca5ba0d..35fc3d7 100644 --- a/features/messenger/src/main/kotlin/app/dapk/st/messenger/state/MessengerState.kt +++ b/features/messenger/src/main/kotlin/app/dapk/st/messenger/state/MessengerState.kt @@ -2,6 +2,7 @@ package app.dapk.st.messenger.state import app.dapk.st.core.Lce import app.dapk.st.design.components.BubbleModel +import app.dapk.st.domain.application.AppRoomState import app.dapk.st.engine.MessengerPageState import app.dapk.st.engine.RoomEvent import app.dapk.st.matrix.common.RoomId @@ -20,10 +21,6 @@ data class MessengerScreenState( val appRoomState: Lce, ) -data class AppRoomState( - val isChatBubble: Boolean -) - data class ViewerState( val event: BubbleModel.Image, ) diff --git a/features/messenger/src/test/kotlin/app/dapk/st/messenger/MessengerReducerTest.kt b/features/messenger/src/test/kotlin/app/dapk/st/messenger/MessengerReducerTest.kt index 6a358e1..946e0e9 100644 --- a/features/messenger/src/test/kotlin/app/dapk/st/messenger/MessengerReducerTest.kt +++ b/features/messenger/src/test/kotlin/app/dapk/st/messenger/MessengerReducerTest.kt @@ -63,6 +63,7 @@ class MessengerReducerTest { fakeMessageOptionsStore.instance, A_ROOM_ID, emptyList(), + mockk(), fakeEventSource, ) } @@ -405,6 +406,7 @@ class MessengerReducerTest { fakeMessageOptionsStore.instance, A_ROOM_ID, initialAttachments = initialAttachments, + appRoomStore = mockk(), fakeEventSource, ) }(block)