accumulating and storing the invite event metadata for member and room names
This commit is contained in:
parent
423ee3afd9
commit
38c9e33a59
|
@ -11,10 +11,8 @@ import app.dapk.st.matrix.sync.RoomInvite
|
||||||
import app.dapk.st.matrix.sync.RoomOverview
|
import app.dapk.st.matrix.sync.RoomOverview
|
||||||
import com.squareup.sqldelight.runtime.coroutines.asFlow
|
import com.squareup.sqldelight.runtime.coroutines.asFlow
|
||||||
import com.squareup.sqldelight.runtime.coroutines.mapToList
|
import com.squareup.sqldelight.runtime.coroutines.mapToList
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
private val json = Json
|
private val json = Json
|
||||||
|
@ -35,7 +33,7 @@ internal class OverviewPersistence(
|
||||||
dispatchers.withIoContext {
|
dispatchers.withIoContext {
|
||||||
database.inviteStateQueries.transaction {
|
database.inviteStateQueries.transaction {
|
||||||
invites.forEach {
|
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()
|
return database.inviteStateQueries.selectAll()
|
||||||
.asFlow()
|
.asFlow()
|
||||||
.mapToList()
|
.mapToList()
|
||||||
.map { it.map { RoomInvite(RoomId(it)) } }
|
.map { it.map { json.decodeFromString(RoomInvite.serializer(), it.blob) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun persist(overviewState: OverviewState) {
|
override suspend fun persist(overviewState: OverviewState) {
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
CREATE TABLE dbInviteState (
|
CREATE TABLE dbInviteState (
|
||||||
room_id TEXT NOT NULL,
|
room_id TEXT NOT NULL,
|
||||||
|
blob TEXT NOT NULL,
|
||||||
PRIMARY KEY (room_id)
|
PRIMARY KEY (room_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
selectAll:
|
selectAll:
|
||||||
SELECT room_id
|
SELECT room_id, blob
|
||||||
FROM dbInviteState;
|
FROM dbInviteState;
|
||||||
|
|
||||||
insert:
|
insert:
|
||||||
INSERT OR REPLACE INTO dbInviteState(room_id)
|
INSERT OR REPLACE INTO dbInviteState(room_id, blob)
|
||||||
VALUES (?);
|
VALUES (?, ?);
|
|
@ -22,6 +22,8 @@ import app.dapk.st.core.LifecycleEffect
|
||||||
import app.dapk.st.core.StartObserving
|
import app.dapk.st.core.StartObserving
|
||||||
import app.dapk.st.core.components.CenteredLoading
|
import app.dapk.st.core.components.CenteredLoading
|
||||||
import app.dapk.st.design.components.*
|
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
|
import app.dapk.st.settings.SettingsActivity
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -122,14 +124,20 @@ private fun Invitations(viewModel: ProfileViewModel, invitations: Page.Invitatio
|
||||||
is Lce.Content -> {
|
is Lce.Content -> {
|
||||||
LazyColumn {
|
LazyColumn {
|
||||||
items(state.value) {
|
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 {
|
Row {
|
||||||
Button(modifier = Modifier.weight(1f), onClick = { viewModel.acceptRoomInvite(it) }) {
|
Button(modifier = Modifier.weight(1f), onClick = { viewModel.rejectRoomInvite(it.roomId) }) {
|
||||||
Text("Accept".uppercase())
|
Text("Reject".uppercase())
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.fillMaxWidth(0.1f))
|
Spacer(modifier = Modifier.fillMaxWidth(0.1f))
|
||||||
Button(modifier = Modifier.weight(1f), onClick = { viewModel.rejectRoomInvite(it) }) {
|
Button(modifier = Modifier.weight(1f), onClick = { viewModel.acceptRoomInvite(it.roomId) }) {
|
||||||
Text("Reject".uppercase())
|
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
|
@Composable
|
||||||
private fun ProfileViewModel.ObserveEvents() {
|
private fun ProfileViewModel.ObserveEvents() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
|
@ -5,6 +5,7 @@ import app.dapk.st.design.components.Route
|
||||||
import app.dapk.st.design.components.SpiderPage
|
import app.dapk.st.design.components.SpiderPage
|
||||||
import app.dapk.st.matrix.common.RoomId
|
import app.dapk.st.matrix.common.RoomId
|
||||||
import app.dapk.st.matrix.room.ProfileService
|
import app.dapk.st.matrix.room.ProfileService
|
||||||
|
import app.dapk.st.matrix.sync.RoomInvite
|
||||||
|
|
||||||
data class ProfileScreenState(
|
data class ProfileScreenState(
|
||||||
val page: SpiderPage<out Page>,
|
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 {
|
object Routes {
|
||||||
val profile = Route<Profile>("Profile")
|
val profile = Route<Profile>("Profile")
|
||||||
|
|
|
@ -50,7 +50,7 @@ class ProfileViewModel(
|
||||||
syncService.invites()
|
syncService.invites()
|
||||||
.onEach {
|
.onEach {
|
||||||
updatePageState<Page.Invitations> {
|
updatePageState<Page.Invitations> {
|
||||||
copy(content = Lce.Content(it.map { it.roomId }))
|
copy(content = Lce.Content(it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.launchPageJob()
|
.launchPageJob()
|
||||||
|
|
|
@ -31,5 +31,18 @@ data class LastMessage(
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class RoomInvite(
|
data class RoomInvite(
|
||||||
|
@SerialName("from") val from: RoomMember,
|
||||||
@SerialName("room_id") val roomId: RoomId,
|
@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()
|
||||||
|
}
|
||||||
|
|
|
@ -230,14 +230,30 @@ internal data class ApiInviteEvents(
|
||||||
sealed class ApiStrippedEvent {
|
sealed class ApiStrippedEvent {
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("m.room.create")
|
@SerialName("m.room.member")
|
||||||
internal data class RoomCreate(
|
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,
|
@SerialName("content") val content: Content,
|
||||||
) : ApiStrippedEvent() {
|
) : ApiStrippedEvent() {
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal data class Content(
|
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) {
|
value class Membership(val value: String) {
|
||||||
fun isJoin() = value == "join"
|
fun isJoin() = value == "join"
|
||||||
fun isInvite() = value == "invite"
|
fun isInvite() = value == "invite"
|
||||||
|
fun isLeave() = value == "leave"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,10 @@ import app.dapk.st.core.CoroutineDispatchers
|
||||||
import app.dapk.st.core.withIoContextAsync
|
import app.dapk.st.core.withIoContextAsync
|
||||||
import app.dapk.st.matrix.common.*
|
import app.dapk.st.matrix.common.*
|
||||||
import app.dapk.st.matrix.common.MatrixLogTag.SYNC
|
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.RoomInvite
|
||||||
import app.dapk.st.matrix.sync.RoomState
|
import app.dapk.st.matrix.sync.RoomState
|
||||||
import app.dapk.st.matrix.sync.internal.request.ApiAccountEvent
|
import app.dapk.st.matrix.sync.internal.request.*
|
||||||
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.room.SideEffectResult
|
import app.dapk.st.matrix.sync.internal.room.SideEffectResult
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
|
|
||||||
|
@ -27,7 +26,7 @@ internal class SyncReducer(
|
||||||
suspend fun reduce(isInitialSync: Boolean, sideEffects: SideEffectResult, response: ApiSyncResponse, userCredentials: UserCredentials): ReducerResult {
|
suspend fun reduce(isInitialSync: Boolean, sideEffects: SideEffectResult, response: ApiSyncResponse, userCredentials: UserCredentials): ReducerResult {
|
||||||
val directMessages = response.directMessages()
|
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 apiUpdatedRooms = response.rooms?.join?.keepRoomsWithChanges()
|
||||||
val apiRoomsToProcess = apiUpdatedRooms?.map { (roomId, apiRoom) ->
|
val apiRoomsToProcess = apiUpdatedRooms?.map { (roomId, apiRoom) ->
|
||||||
logger.matrixLog(SYNC, "reducing: $roomId")
|
logger.matrixLog(SYNC, "reducing: $roomId")
|
||||||
|
@ -52,6 +51,20 @@ internal class SyncReducer(
|
||||||
|
|
||||||
return ReducerResult((apiRoomsToProcess + roomsWithSideEffects).awaitAll().filterNotNull(), invites)
|
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 {
|
private fun Map<RoomId, ApiSyncRoom>.keepRoomsWithChanges() = this.filter {
|
||||||
|
|
Loading…
Reference in New Issue