Add app room state persistence and allow rooms to be marked with bubbles

This commit is contained in:
Adam Brown 2023-01-26 21:30:46 +00:00
parent 6a1d360036
commit d44c331eab
14 changed files with 87 additions and 28 deletions

View File

@ -180,6 +180,7 @@ internal class FeatureModules internal constructor(
chatEngineModule.engine, chatEngineModule.engine,
context, context,
storeModule.value.messageStore(), storeModule.value.messageStore(),
storeModule.value.appRoomStore(),
deviceMeta, deviceMeta,
) )
} }

View File

@ -3,6 +3,7 @@ package app.dapk.st.domain
import app.dapk.db.app.StDb import app.dapk.db.app.StDb
import app.dapk.st.core.CoroutineDispatchers import app.dapk.st.core.CoroutineDispatchers
import app.dapk.st.core.Preferences 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.EventLogPersistence
import app.dapk.st.domain.application.eventlog.LoggingStore import app.dapk.st.domain.application.eventlog.LoggingStore
import app.dapk.st.domain.application.message.MessageOptionsStore import app.dapk.st.domain.application.message.MessageOptionsStore
@ -41,4 +42,6 @@ class StoreModule(
fun messageStore(): MessageOptionsStore = MessageOptionsStore(cachingPreferences) fun messageStore(): MessageOptionsStore = MessageOptionsStore(cachingPreferences)
fun appRoomStore(): AppRoomPersistence = AppRoomPersistence(database, coroutineDispatchers)
} }

View File

@ -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<AppRoomState> {
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
)

View File

@ -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)
);

View File

@ -1,18 +1,14 @@
CREATE TABLE dbAppRoom ( CREATE TABLE dbAppRoom (
room_id TEXT NOT NULL, 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) PRIMARY KEY (room_id)
); );
markBubble: updateBubble:
INSERT OR REPLACE INTO dbAppRoom(room_id, is_bubble)
VALUES (?,?);
unmarkBubble:
INSERT OR REPLACE INTO dbAppRoom(room_id, is_bubble) INSERT OR REPLACE INTO dbAppRoom(room_id, is_bubble)
VALUES (?,?); VALUES (?,?);
select: select:
SELECT is_bubble SELECT *
FROM dbAppRoom FROM dbAppRoom
WHERE room_id = ?; WHERE room_id = ?;

View File

@ -1,4 +0,0 @@
CREATE TABLE IF NOT EXISTS dbMutedRoom (
room_id TEXT NOT NULL,
PRIMARY KEY (room_id)
);

View File

@ -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)
);

View File

@ -5,6 +5,7 @@ import android.content.Context
import app.dapk.st.core.DeviceMeta import app.dapk.st.core.DeviceMeta
import app.dapk.st.core.JobBag import app.dapk.st.core.JobBag
import app.dapk.st.core.ProvidableModule import app.dapk.st.core.ProvidableModule
import app.dapk.st.domain.application.AppRoomPersistence
import app.dapk.st.state.createStateViewModel import app.dapk.st.state.createStateViewModel
import app.dapk.st.domain.application.message.MessageOptionsStore import app.dapk.st.domain.application.message.MessageOptionsStore
import app.dapk.st.engine.ChatEngine import app.dapk.st.engine.ChatEngine
@ -16,6 +17,7 @@ class MessengerModule(
private val chatEngine: ChatEngine, private val chatEngine: ChatEngine,
private val context: Context, private val context: Context,
private val messageOptionsStore: MessageOptionsStore, private val messageOptionsStore: MessageOptionsStore,
private val appRoomStore: AppRoomPersistence,
val deviceMeta: DeviceMeta, val deviceMeta: DeviceMeta,
) : ProvidableModule { ) : ProvidableModule {
@ -29,6 +31,7 @@ class MessengerModule(
messageOptionsStore, messageOptionsStore,
RoomId(launchPayload.roomId), RoomId(launchPayload.roomId),
launchPayload.attachments, launchPayload.attachments,
appRoomStore,
it it
) )
} }

View File

@ -122,13 +122,17 @@ internal fun MessengerScreen(
} }
viewModel.dispatch(action) viewModel.dispatch(action)
} }
BooleanOption(value = it.isMuted, trueText = "Disable bubble", falseText = "Enable bubble") {
state.appRoomState.takeIfContent()?.let {
BooleanOption(value = it.isChatBubble, trueText = "Disable bubble", falseText = "Enable bubble") {
val action = when (it) { val action = when (it) {
true -> ChatBubble.Disable true -> ChatBubble.Disable
false -> ChatBubble.Enable false -> ChatBubble.Enable
} }
viewModel.dispatch(action) viewModel.dispatch(action)
} }
}
DropdownMenuItem(text = { Text("Leave room", color = MaterialTheme.colorScheme.onSecondaryContainer) }, onClick = { DropdownMenuItem(text = { Text("Leave room", color = MaterialTheme.colorScheme.onSecondaryContainer) }, onClick = {
viewModel.dispatch(ScreenAction.LeaveRoom) viewModel.dispatch(ScreenAction.LeaveRoom)
}) })

View File

@ -1,6 +1,7 @@
package app.dapk.st.messenger.state package app.dapk.st.messenger.state
import app.dapk.st.design.components.BubbleModel 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.MessengerPageState
import app.dapk.st.engine.RoomEvent import app.dapk.st.engine.RoomEvent
import app.dapk.st.navigator.MessageAttachment import app.dapk.st.navigator.MessageAttachment
@ -37,6 +38,7 @@ sealed interface ComponentLifecycle : Action {
sealed interface MessagesStateChange : Action { sealed interface MessagesStateChange : Action {
data class Content(val content: MessengerPageState) : MessagesStateChange data class Content(val content: MessengerPageState) : MessagesStateChange
data class AppRoomContent(val content: AppRoomState) : MessagesStateChange
data class MuteContent(val isMuted: Boolean) : MessagesStateChange data class MuteContent(val isMuted: Boolean) : MessagesStateChange
data class ChatBubbleContent(val isChatBubble: Boolean) : MessagesStateChange data class ChatBubbleContent(val isChatBubble: Boolean) : MessagesStateChange
} }

View File

@ -1,12 +1,14 @@
package app.dapk.st.messenger.state package app.dapk.st.messenger.state
import android.os.Build import android.os.Build
import android.util.Log
import app.dapk.st.core.DeviceMeta import app.dapk.st.core.DeviceMeta
import app.dapk.st.core.JobBag import app.dapk.st.core.JobBag
import app.dapk.st.core.Lce import app.dapk.st.core.Lce
import app.dapk.st.core.asString import app.dapk.st.core.asString
import app.dapk.st.core.extensions.takeIfContent import app.dapk.st.core.extensions.takeIfContent
import app.dapk.st.design.components.BubbleModel 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.domain.application.message.MessageOptionsStore
import app.dapk.st.engine.ChatEngine import app.dapk.st.engine.ChatEngine
import app.dapk.st.engine.MessengerPageState import app.dapk.st.engine.MessengerPageState
@ -29,6 +31,7 @@ internal fun messengerReducer(
messageOptionsStore: MessageOptionsStore, messageOptionsStore: MessageOptionsStore,
roomId: RoomId, roomId: RoomId,
initialAttachments: List<MessageAttachment>?, initialAttachments: List<MessageAttachment>?,
appRoomStore: AppRoomPersistence,
eventEmitter: suspend (MessengerEvent) -> Unit, eventEmitter: suspend (MessengerEvent) -> Unit,
): ReducerFactory<MessengerScreenState> { ): ReducerFactory<MessengerScreenState> {
return createReducer( return createReducer(
@ -45,6 +48,14 @@ internal fun messengerReducer(
val state = getState() val state = getState()
when (action) { when (action) {
is ComponentLifecycle.Visible -> { is ComponentLifecycle.Visible -> {
jobBag.replace(
"appRoomState",
appRoomStore.observe(state.roomId)
.onEach { dispatch(MessagesStateChange.AppRoomContent(it)) }
.launchIn(coroutineScope)
)
jobBag.replace( jobBag.replace(
"messages", chatEngine.messages(state.roomId, disableReadReceipts = messageOptionsStore.isReadReceiptsDisabled()) "messages", chatEngine.messages(state.roomId, disableReadReceipts = messageOptionsStore.isReadReceiptsDisabled())
.onEach { dispatch(MessagesStateChange.Content(it)) } .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 -> change(MessagesStateChange.Content::class) { action, state ->
state.copy(roomState = Lce.Content(action.content)) state.copy(roomState = Lce.Content(action.content))
}, },
@ -174,9 +189,11 @@ internal fun messengerReducer(
}, },
async(ScreenAction.ChatBubble::class) { action -> async(ScreenAction.ChatBubble::class) { action ->
Log.e("!!!", "$action")
val state = getState()
when (action) { when (action) {
ScreenAction.ChatBubble.Disable -> {} ScreenAction.ChatBubble.Disable -> appRoomStore.unmarkBubble(state.roomId)
ScreenAction.ChatBubble.Enable -> {} ScreenAction.ChatBubble.Enable -> appRoomStore.markBubble(state.roomId)
} }
dispatch( dispatch(

View File

@ -2,6 +2,7 @@ package app.dapk.st.messenger.state
import app.dapk.st.core.Lce import app.dapk.st.core.Lce
import app.dapk.st.design.components.BubbleModel 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.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
@ -20,10 +21,6 @@ data class MessengerScreenState(
val appRoomState: Lce<AppRoomState>, val appRoomState: Lce<AppRoomState>,
) )
data class AppRoomState(
val isChatBubble: Boolean
)
data class ViewerState( data class ViewerState(
val event: BubbleModel.Image, val event: BubbleModel.Image,
) )

View File

@ -63,6 +63,7 @@ class MessengerReducerTest {
fakeMessageOptionsStore.instance, fakeMessageOptionsStore.instance,
A_ROOM_ID, A_ROOM_ID,
emptyList(), emptyList(),
mockk(),
fakeEventSource, fakeEventSource,
) )
} }
@ -405,6 +406,7 @@ class MessengerReducerTest {
fakeMessageOptionsStore.instance, fakeMessageOptionsStore.instance,
A_ROOM_ID, A_ROOM_ID,
initialAttachments = initialAttachments, initialAttachments = initialAttachments,
appRoomStore = mockk(),
fakeEventSource, fakeEventSource,
) )
}(block) }(block)