speed up message render by marking room read state async

This commit is contained in:
Adam Brown 2022-03-07 19:56:24 +00:00
parent 5923e55a6b
commit ccecfb08e0
7 changed files with 70 additions and 56 deletions

View File

@ -15,7 +15,9 @@ class MessengerModule(
private val roomStore: RoomStore,
) : ProvidableModule {
fun messengerViewModel(): MessengerViewModel {
return MessengerViewModel(syncService, messageService, roomService, roomStore, credentialsStore)
internal fun messengerViewModel(): MessengerViewModel {
return MessengerViewModel(messageService, roomService, roomStore, credentialsStore, timelineUseCase())
}
private fun timelineUseCase() = TimelineUseCase(syncService, messageService, roomService, MergeWithLocalEchosUseCaseImpl())
}

View File

@ -39,7 +39,7 @@ import app.dapk.st.navigator.Navigator
import kotlinx.coroutines.launch
@Composable
fun MessengerScreen(roomId: RoomId, viewModel: MessengerViewModel, navigator: Navigator) {
internal fun MessengerScreen(roomId: RoomId, viewModel: MessengerViewModel, navigator: Navigator) {
val state = viewModel.state
viewModel.ObserveEvents()

View File

@ -4,25 +4,25 @@ import androidx.lifecycle.viewModelScope
import app.dapk.st.core.Lce
import app.dapk.st.core.extensions.takeIfContent
import app.dapk.st.matrix.common.CredentialsStore
import app.dapk.st.matrix.common.EventId
import app.dapk.st.matrix.common.RoomId
import app.dapk.st.matrix.common.UserId
import app.dapk.st.matrix.message.MessageService
import app.dapk.st.matrix.room.RoomService
import app.dapk.st.matrix.sync.RoomEvent
import app.dapk.st.matrix.sync.RoomStore
import app.dapk.st.matrix.sync.SyncService
import app.dapk.st.viewmodel.DapkViewModel
import kotlinx.coroutines.Job
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
class MessengerViewModel(
syncService: SyncService,
internal class MessengerViewModel(
private val messageService: MessageService,
private val roomService: RoomService,
private val roomStore: RoomStore,
private val credentialsStore: CredentialsStore,
private val useCase: TimelineUseCase,
) : DapkViewModel<MessengerScreenState, MessengerEvent>(
initialState = MessengerScreenState(
roomId = null,
@ -32,53 +32,58 @@ class MessengerViewModel(
) {
private var syncJob: Job? = null
private val useCase: TimelineUseCase = TimelineUseCase(syncService, messageService, roomService, MergeWithLocalEchosUseCaseImpl())
fun post(action: MessengerAction) {
when (action) {
is MessengerAction.ComposerTextUpdate -> {
updateState { copy(composerState = ComposerState.Text(action.newValue)) }
}
is MessengerAction.OnMessengerVisible -> {
updateState { copy(roomId = action.roomId) }
is MessengerAction.OnMessengerVisible -> start(action)
MessengerAction.OnMessengerGone -> syncJob?.cancel()
is MessengerAction.ComposerTextUpdate -> updateState { copy(composerState = ComposerState.Text(action.newValue)) }
MessengerAction.ComposerSendText -> sendMessage()
}
}
syncJob = viewModelScope.launch {
useCase.startSyncing().collect()
private fun start(action: MessengerAction.OnMessengerVisible) {
updateState { copy(roomId = action.roomId) }
syncJob = viewModelScope.launch {
roomStore.markRead(action.roomId)
val credentials = credentialsStore.credentials()!!
var lastKnownReadEvent: EventId? = null
useCase.state(action.roomId, credentials.userId).distinctUntilChanged().onEach { state ->
state.lastestMessageEventFromOthers(self = credentials.userId)?.let {
if (lastKnownReadEvent != it) {
updateRoomReadStateAsync(latestReadEvent = it, state)
lastKnownReadEvent = it
}
}
viewModelScope.launch {
roomStore.markRead(action.roomId)
updateState { copy(roomState = Lce.Content(state)) }
}.collect()
}
}
val credentials = credentialsStore.credentials()!!
useCase.state(action.roomId, credentials.userId).distinctUntilChanged().onEach { state ->
state.roomState.events.filterIsInstance<RoomEvent.Message>().filterNot { it.author.id == credentials.userId }.firstOrNull()?.let {
roomService.markFullyRead(state.roomState.roomOverview.roomId, it.eventId)
roomStore.markRead(state.roomState.roomOverview.roomId)
}
updateState { copy(roomState = Lce.Content(state)) }
}.collect()
}
}
MessengerAction.OnMessengerGone -> {
syncJob?.cancel()
}
MessengerAction.ComposerSendText -> {
when (val composerState = state.composerState) {
is ComposerState.Text -> {
val copy = composerState.copy()
updateState { copy(composerState = composerState.copy(value = "")) }
private fun CoroutineScope.updateRoomReadStateAsync(latestReadEvent: EventId, state: MessengerState): Deferred<Unit> {
return async {
roomService.markFullyRead(state.roomState.roomOverview.roomId, latestReadEvent)
roomStore.markRead(state.roomState.roomOverview.roomId)
}
}
state.roomState.takeIfContent()?.let { content ->
val roomState = content.roomState
viewModelScope.launch {
messageService.scheduleMessage(
MessageService.Message.TextMessage(
MessageService.Message.Content.TextContent(body = copy.value),
roomId = roomState.roomOverview.roomId,
sendEncrypted = roomState.roomOverview.isEncrypted,
)
)
}
}
private fun sendMessage() {
when (val composerState = state.composerState) {
is ComposerState.Text -> {
val copy = composerState.copy()
updateState { copy(composerState = composerState.copy(value = "")) }
state.roomState.takeIfContent()?.let { content ->
val roomState = content.roomState
viewModelScope.launch {
messageService.scheduleMessage(
MessageService.Message.TextMessage(
MessageService.Message.Content.TextContent(body = copy.value),
roomId = roomState.roomOverview.roomId,
sendEncrypted = roomState.roomOverview.isEncrypted,
)
)
}
}
}
@ -87,6 +92,12 @@ class MessengerViewModel(
}
private fun MessengerState.lastestMessageEventFromOthers(self: UserId) = this.roomState.events
.filterIsInstance<RoomEvent.Message>()
.filterNot { it.author.id == self }
.firstOrNull()
?.eventId
sealed interface MessengerAction {
data class ComposerTextUpdate(val newValue: String) : MessengerAction
object ComposerSendText : MessengerAction

View File

@ -23,12 +23,13 @@ internal class TimelineUseCase(
private val mergeWithLocalEchosUseCase: MergeWithLocalEchosUseCase
) {
suspend fun startSyncing(): Flow<Unit> {
return syncService.startSyncing()
}
suspend fun state(roomId: RoomId, userId: UserId): Flow<MessengerState> {
return combine(syncService.room(roomId), messageService.localEchos(roomId), syncService.events()) { roomState, localEchos, events ->
return combine(
syncService.startSyncing(),
syncService.room(roomId),
messageService.localEchos(roomId),
syncService.events()
) { _, roomState, localEchos, events ->
MessengerState(
roomState = when {
localEchos.isEmpty() -> roomState

View File

@ -72,7 +72,7 @@ class NotificationsUseCase(
}
if (summaryNotification == null) {
notificationManager.cancel(101)
notificationManager.cancel(SUMMARY_NOTIFICATION_ID)
}
notifications.forEach {

View File

@ -22,7 +22,7 @@ interface SyncService : MatrixService {
suspend fun invites(): Flow<InviteState>
suspend fun overview(): Flow<OverviewState>
suspend fun room(roomId: RoomId): Flow<RoomState>
suspend fun startSyncing(): Flow<Unit>
fun startSyncing(): Flow<Unit>
suspend fun events(): Flow<List<SyncEvent>>
suspend fun observeEvent(eventId: EventId): Flow<EventId>
suspend fun forceManualRefresh(roomIds: List<RoomId>)

View File

@ -98,7 +98,7 @@ internal class DefaultSyncService(
}
}
override suspend fun startSyncing() = syncFlow
override fun startSyncing() = syncFlow
override suspend fun invites() = overviewStore.latestInvites()
override suspend fun overview() = overviewStore.latest()
override suspend fun room(roomId: RoomId) = roomStore.latest(roomId)