accumulating and storing the invite event metadata for member and room names

This commit is contained in:
Adam Brown 2022-03-16 22:36:42 +00:00
parent 423ee3afd9
commit 38c9e33a59
8 changed files with 74 additions and 21 deletions

View File

@ -11,10 +11,8 @@ import app.dapk.st.matrix.sync.RoomInvite
import app.dapk.st.matrix.sync.RoomOverview
import com.squareup.sqldelight.runtime.coroutines.asFlow
import com.squareup.sqldelight.runtime.coroutines.mapToList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
private val json = Json
@ -35,7 +33,7 @@ internal class OverviewPersistence(
dispatchers.withIoContext {
database.inviteStateQueries.transaction {
invites.forEach {
database.inviteStateQueries.insert(it.roomId.value)
database.inviteStateQueries.insert(it.roomId.value, json.encodeToString(RoomInvite.serializer(), it))
}
}
}
@ -45,7 +43,7 @@ internal class OverviewPersistence(
return database.inviteStateQueries.selectAll()
.asFlow()
.mapToList()
.map { it.map { RoomInvite(RoomId(it)) } }
.map { it.map { json.decodeFromString(RoomInvite.serializer(), it.blob) } }
}
override suspend fun persist(overviewState: OverviewState) {

View File

@ -1,12 +1,13 @@
CREATE TABLE dbInviteState (
room_id TEXT NOT NULL,
blob TEXT NOT NULL,
PRIMARY KEY (room_id)
);
selectAll:
SELECT room_id
SELECT room_id, blob
FROM dbInviteState;
insert:
INSERT OR REPLACE INTO dbInviteState(room_id)
VALUES (?);
INSERT OR REPLACE INTO dbInviteState(room_id, blob)
VALUES (?, ?);

View File

@ -22,6 +22,8 @@ import app.dapk.st.core.LifecycleEffect
import app.dapk.st.core.StartObserving
import app.dapk.st.core.components.CenteredLoading
import app.dapk.st.design.components.*
import app.dapk.st.matrix.sync.InviteMeta
import app.dapk.st.matrix.sync.RoomInvite
import app.dapk.st.settings.SettingsActivity
@Composable
@ -122,14 +124,20 @@ private fun Invitations(viewModel: ProfileViewModel, invitations: Page.Invitatio
is Lce.Content -> {
LazyColumn {
items(state.value) {
TextRow(title = it.value, includeDivider = false) {
val text = when (val meta = it.inviteMeta) {
InviteMeta.DirectMessage -> "${it.inviterName()} has invited you to chat"
is InviteMeta.Room -> "${it.inviterName()} has invited you to ${meta.roomName ?: "unnamed room"}"
}
TextRow(title = text, includeDivider = false) {
Spacer(modifier = Modifier.height(4.dp))
Row {
Button(modifier = Modifier.weight(1f), onClick = { viewModel.acceptRoomInvite(it) }) {
Text("Accept".uppercase())
Button(modifier = Modifier.weight(1f), onClick = { viewModel.rejectRoomInvite(it.roomId) }) {
Text("Reject".uppercase())
}
Spacer(modifier = Modifier.fillMaxWidth(0.1f))
Button(modifier = Modifier.weight(1f), onClick = { viewModel.rejectRoomInvite(it) }) {
Text("Reject".uppercase())
Button(modifier = Modifier.weight(1f), onClick = { viewModel.acceptRoomInvite(it.roomId) }) {
Text("Accept".uppercase())
}
}
}
@ -141,6 +149,8 @@ private fun Invitations(viewModel: ProfileViewModel, invitations: Page.Invitatio
}
}
private fun RoomInvite.inviterName() = this.from.displayName?.let { "$it (${this.from.id.value})" } ?: this.from.id.value
@Composable
private fun ProfileViewModel.ObserveEvents() {
val context = LocalContext.current

View File

@ -5,6 +5,7 @@ import app.dapk.st.design.components.Route
import app.dapk.st.design.components.SpiderPage
import app.dapk.st.matrix.common.RoomId
import app.dapk.st.matrix.room.ProfileService
import app.dapk.st.matrix.sync.RoomInvite
data class ProfileScreenState(
val page: SpiderPage<out Page>,
@ -18,7 +19,7 @@ sealed interface Page {
)
}
data class Invitations(val content: Lce<List<RoomId>>): Page
data class Invitations(val content: Lce<List<RoomInvite>>): Page
object Routes {
val profile = Route<Profile>("Profile")

View File

@ -50,7 +50,7 @@ class ProfileViewModel(
syncService.invites()
.onEach {
updatePageState<Page.Invitations> {
copy(content = Lce.Content(it.map { it.roomId }))
copy(content = Lce.Content(it))
}
}
.launchPageJob()

View File

@ -31,5 +31,18 @@ data class LastMessage(
@Serializable
data class RoomInvite(
@SerialName("from") val from: RoomMember,
@SerialName("room_id") val roomId: RoomId,
@SerialName("meta") val inviteMeta: InviteMeta,
)
@Serializable
sealed class InviteMeta {
@Serializable
@SerialName("direct_message")
object DirectMessage : InviteMeta()
@Serializable
@SerialName("room")
data class Room(val roomName: String? = null) : InviteMeta()
}

View File

@ -230,14 +230,30 @@ internal data class ApiInviteEvents(
sealed class ApiStrippedEvent {
@Serializable
@SerialName("m.room.create")
internal data class RoomCreate(
@SerialName("m.room.member")
internal data class RoomMember(
@SerialName("content") val content: Content,
@SerialName("sender") val sender: UserId,
) : ApiStrippedEvent() {
@Serializable
internal data class Content(
@SerialName("displayname") val displayName: String? = null,
@SerialName("membership") val membership: ApiTimelineEvent.RoomMember.Content.Membership? = null,
@SerialName("is_direct") val isDirect: Boolean? = null,
@SerialName("avatar_url") val avatarUrl: MxUrl? = null,
)
}
@Serializable
@SerialName("m.room.name")
internal data class RoomName(
@SerialName("content") val content: Content,
) : ApiStrippedEvent() {
@Serializable
internal data class Content(
@SerialName("type") val type: String? = null
@SerialName("name") val name: String? = null
)
}
@ -407,6 +423,7 @@ internal sealed class ApiTimelineEvent {
value class Membership(val value: String) {
fun isJoin() = value == "join"
fun isInvite() = value == "invite"
fun isLeave() = value == "leave"
}
}

View File

@ -4,11 +4,10 @@ import app.dapk.st.core.CoroutineDispatchers
import app.dapk.st.core.withIoContextAsync
import app.dapk.st.matrix.common.*
import app.dapk.st.matrix.common.MatrixLogTag.SYNC
import app.dapk.st.matrix.sync.InviteMeta
import app.dapk.st.matrix.sync.RoomInvite
import app.dapk.st.matrix.sync.RoomState
import app.dapk.st.matrix.sync.internal.request.ApiAccountEvent
import app.dapk.st.matrix.sync.internal.request.ApiSyncResponse
import app.dapk.st.matrix.sync.internal.request.ApiSyncRoom
import app.dapk.st.matrix.sync.internal.request.*
import app.dapk.st.matrix.sync.internal.room.SideEffectResult
import kotlinx.coroutines.awaitAll
@ -27,7 +26,7 @@ internal class SyncReducer(
suspend fun reduce(isInitialSync: Boolean, sideEffects: SideEffectResult, response: ApiSyncResponse, userCredentials: UserCredentials): ReducerResult {
val directMessages = response.directMessages()
val invites = response.rooms?.invite?.keys?.map { RoomInvite(it) } ?: emptyList()
val invites = response.rooms?.invite?.map { roomInvite(it, userCredentials) } ?: emptyList()
val apiUpdatedRooms = response.rooms?.join?.keepRoomsWithChanges()
val apiRoomsToProcess = apiUpdatedRooms?.map { (roomId, apiRoom) ->
logger.matrixLog(SYNC, "reducing: $roomId")
@ -52,6 +51,20 @@ internal class SyncReducer(
return ReducerResult((apiRoomsToProcess + roomsWithSideEffects).awaitAll().filterNotNull(), invites)
}
private fun roomInvite(entry: Map.Entry<RoomId, ApiSyncRoomInvite>, userCredentials: UserCredentials): RoomInvite {
val memberEvents = entry.value.state.events.filterIsInstance<ApiStrippedEvent.RoomMember>()
val invitee = memberEvents.first { it.content.membership?.isInvite() ?: false }
val from = memberEvents.first { it.sender == invitee.sender }
return RoomInvite(
RoomMember(from.sender, from.content.displayName, from.content.avatarUrl?.convertMxUrToUrl(userCredentials.homeServer)?.let { AvatarUrl(it) }),
roomId = entry.key,
inviteMeta = when (invitee.content.isDirect) {
true -> InviteMeta.DirectMessage
null, false -> InviteMeta.Room(entry.value.state.events.filterIsInstance<ApiStrippedEvent.RoomName>().firstOrNull()?.content?.name)
},
)
}
}
private fun Map<RoomId, ApiSyncRoom>.keepRoomsWithChanges() = this.filter {