Browse Source

Start filling MatrixRoom class and use it in MessagesScreen

feature/bma/flipper
ganfra 2 years ago
parent
commit
11b74c0279
  1. 21
      features/messages/src/main/java/io/element/android/x/features/messages/MessagesScreen.kt
  2. 61
      features/messages/src/main/java/io/element/android/x/features/messages/MessagesViewModel.kt
  3. 5
      features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesViewState.kt
  4. 2
      features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt
  5. 11
      libraries/matrix/src/main/java/io/element/android/x/matrix/Matrix.kt
  6. 44
      libraries/matrix/src/main/java/io/element/android/x/matrix/MatrixClient.kt
  7. 41
      libraries/matrix/src/main/java/io/element/android/x/matrix/room/MatrixRoom.kt
  8. 40
      libraries/matrix/src/main/java/io/element/android/x/matrix/room/RoomSummaryDataSource.kt
  9. 30
      libraries/matrix/src/main/java/io/element/android/x/matrix/room/RoomSummaryDetailsFactory.kt
  10. 1
      libraries/matrix/src/main/java/io/element/android/x/matrix/room/message/RoomMessageFactory.kt
  11. 20
      libraries/matrix/src/main/java/io/element/android/x/matrix/sync/SlidingSyncObserverProxy.kt

21
features/messages/src/main/java/io/element/android/x/features/messages/MessagesScreen.kt

@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
package io.element.android.x.features.messages
import Avatar
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.*
@ -12,18 +13,23 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll @@ -12,18 +13,23 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import com.airbnb.mvrx.compose.collectAsState
import com.airbnb.mvrx.compose.mavericksViewModel
import io.element.android.x.core.data.LogCompositions
import io.element.android.x.designsystem.components.avatar.AvatarData
import io.element.android.x.features.messages.model.MessagesViewState
@Composable
fun MessagesScreen(roomId: String) {
val viewModel: MessagesViewModel = mavericksViewModel(argsFactory = { roomId })
LogCompositions(tag = "MessagesScreen", msg = "Root")
val roomTitle by viewModel.collectAsState(prop1 = MessagesViewState::roomTitle)
MessagesContent(roomTitle)
val roomTitle by viewModel.collectAsState(MessagesViewState::roomName)
val roomAvatar by viewModel.collectAsState(MessagesViewState::roomAvatar)
MessagesContent(roomTitle, roomAvatar)
}
@Composable
fun MessagesContent(roomTitle: String) {
fun MessagesContent(
roomTitle: String?,
roomAvatar: AvatarData?
) {
val appBarState = rememberTopAppBarState()
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(appBarState)
LogCompositions(tag = "RoomListScreen", msg = "Content")
@ -31,7 +37,14 @@ fun MessagesContent(roomTitle: String) { @@ -31,7 +37,14 @@ fun MessagesContent(roomTitle: String) {
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
TopAppBar(
title = { Text(text = roomTitle) }
navigationIcon = {
if (roomAvatar != null) {
IconButton(onClick = {}) {
Avatar(roomAvatar)
}
}
},
title = { Text(text = roomTitle ?: "") }
)
},
content = { padding ->

61
features/messages/src/main/java/io/element/android/x/features/messages/MessagesViewModel.kt

@ -1,48 +1,69 @@ @@ -1,48 +1,69 @@
package io.element.android.x.features.messages
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MavericksViewModel
import com.airbnb.mvrx.Success
import io.element.android.x.core.data.parallelMap
import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import io.element.android.x.designsystem.components.avatar.AvatarData
import io.element.android.x.designsystem.components.avatar.AvatarSize
import io.element.android.x.features.messages.model.MessagesViewState
import io.element.android.x.matrix.MatrixClient
import io.element.android.x.matrix.MatrixInstance
import io.element.android.x.matrix.room.RoomSummary
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import io.element.android.x.matrix.room.MatrixRoom
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.matrix.rustcomponents.sdk.mediaSourceFromUrl
class MessagesViewModel(initialState: MessagesViewState) :
class MessagesViewModel(
private val client: MatrixClient,
private val room: MatrixRoom,
private val initialState: MessagesViewState
) :
MavericksViewModel<MessagesViewState>(initialState) {
private val matrix = MatrixInstance.getInstance()
companion object : MavericksViewModelFactory<MessagesViewModel, MessagesViewState> {
override fun create(
viewModelContext: ViewModelContext,
state: MessagesViewState
): MessagesViewModel? {
val matrix = MatrixInstance.getInstance()
val client = matrix.activeClient()
val room = client.getRoom(state.roomId) ?: return null
return MessagesViewModel(client, room, state)
}
}
init {
handleInit()
}
private fun handleInit() {
viewModelScope.launch {
}
room.syncUpdateFlow()
.onEach {
val avatarData =
loadAvatarData(room.name ?: room.roomId.value, room.avatarUrl, AvatarSize.SMALL)
setState {
copy(
roomName = room.name, roomAvatar = avatarData,
)
}
}.launchIn(viewModelScope)
}
private suspend fun loadAvatarData(
client: MatrixClient,
name: String,
url: String?,
size: AvatarSize = AvatarSize.MEDIUM
): AvatarData {
val mediaContent = url?.let {
val mediaSource = mediaSourceFromUrl(it)
client.loadMediaThumbnailForSource(mediaSource, size.value.toLong(), size.value.toLong())
client.loadMediaThumbnailForSource(
mediaSource,
size.value.toLong(),
size.value.toLong()
)
}
return mediaContent?.fold(
{ it },
@ -52,10 +73,6 @@ class MessagesViewModel(initialState: MessagesViewState) : @@ -52,10 +73,6 @@ class MessagesViewModel(initialState: MessagesViewState) :
}
}
private suspend fun getClient(): MatrixClient {
return matrix.matrixClient().first().get()
}
override fun onCleared() {
super.onCleared()
}

5
features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesViewState.kt

@ -2,15 +2,14 @@ package io.element.android.x.features.messages.model @@ -2,15 +2,14 @@ package io.element.android.x.features.messages.model
import com.airbnb.mvrx.MavericksState
import io.element.android.x.designsystem.components.avatar.AvatarData
import io.element.android.x.matrix.core.RoomId
data class MessagesViewState(
val roomId: String,
val roomTitle: String = "",
val roomName: String? = null,
val roomAvatar: AvatarData? = null
) : MavericksState {
@Suppress("unused")
constructor(roomId: String) : this(roomId = roomId, roomTitle = "", roomAvatar = null)
constructor(roomId: String) : this(roomId = roomId, roomName = null, roomAvatar = null)
}

2
features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt

@ -131,7 +131,7 @@ class RoomListViewModel(initialState: RoomListViewState) : @@ -131,7 +131,7 @@ class RoomListViewModel(initialState: RoomListViewState) :
}
private suspend fun getClient(): MatrixClient {
return matrix.matrixClient().first().get()
return matrix.client().first().get()
}
override fun onCleared() {

11
libraries/matrix/src/main/java/io/element/android/x/matrix/Matrix.kt

@ -12,10 +12,10 @@ import org.matrix.rustcomponents.sdk.AuthenticationService @@ -12,10 +12,10 @@ import org.matrix.rustcomponents.sdk.AuthenticationService
import org.matrix.rustcomponents.sdk.Client
import org.matrix.rustcomponents.sdk.ClientBuilder
import java.io.File
import java.util.Optional
import java.util.*
class Matrix(
coroutineScope: CoroutineScope,
private val coroutineScope: CoroutineScope,
context: Context,
) {
private val coroutineDispatchers = CoroutineDispatchers(
@ -44,10 +44,14 @@ class Matrix( @@ -44,10 +44,14 @@ class Matrix(
return isLoggedIn
}
fun matrixClient(): Flow<Optional<MatrixClient>> {
fun client(): Flow<Optional<MatrixClient>> {
return matrixClient
}
fun activeClient(): MatrixClient {
return matrixClient.value.get()
}
suspend fun restoreSession() = withContext(coroutineDispatchers.io) {
sessionStore.getStoredData()
?.let { sessionData ->
@ -80,6 +84,7 @@ class Matrix( @@ -80,6 +84,7 @@ class Matrix(
return MatrixClient(
client = client,
sessionStore = sessionStore,
coroutineScope = coroutineScope,
dispatchers = coroutineDispatchers
).also {
matrixClient.value = Optional.of(it)

44
libraries/matrix/src/main/java/io/element/android/x/matrix/MatrixClient.kt

@ -2,9 +2,14 @@ package io.element.android.x.matrix @@ -2,9 +2,14 @@ package io.element.android.x.matrix
import io.element.android.x.core.data.CoroutineDispatchers
import io.element.android.x.matrix.core.UserId
import io.element.android.x.matrix.room.MatrixRoom
import io.element.android.x.matrix.room.RoomSummaryDataSource
import io.element.android.x.matrix.room.RoomSummaryDetailsFactory
import io.element.android.x.matrix.room.RustRoomSummaryDataSource
import io.element.android.x.matrix.room.message.RoomMessageFactory
import io.element.android.x.matrix.session.SessionStore
import io.element.android.x.matrix.sync.SlidingSyncObserverProxy
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.*
import timber.log.Timber
@ -13,6 +18,7 @@ import java.io.Closeable @@ -13,6 +18,7 @@ import java.io.Closeable
class MatrixClient internal constructor(
private val client: Client,
private val sessionStore: SessionStore,
private val coroutineScope: CoroutineScope,
private val dispatchers: CoroutineDispatchers,
) : Closeable {
@ -30,19 +36,15 @@ class MatrixClient internal constructor( @@ -30,19 +36,15 @@ class MatrixClient internal constructor(
}
}
private val slidingSyncObserver = object : SlidingSyncObserver {
override fun didReceiveSyncUpdate(summary: UpdateSummary) {
Timber.v("didReceiveSyncUpdate=$summary on Thread: ${Thread.currentThread()}")
roomSummaryDataSource.updateRoomsWithIdentifiers(summary.rooms)
}
}
private val slidingSyncView = SlidingSyncViewBuilder()
.timelineLimit(limit = 1u)
.requiredState(requiredState = listOf(
RequiredState(key = "m.room.avatar", value = ""),
RequiredState(key = "m.room.encryption", value = ""),
))
.requiredState(
requiredState = listOf(
RequiredState(key = "m.room.avatar", value = ""),
RequiredState(key = "m.room.name", value = ""),
RequiredState(key = "m.room.encryption", value = ""),
)
)
.name(name = "HomeScreenView")
.syncMode(mode = SlidingSyncMode.FULL_SYNC)
.build()
@ -54,15 +56,28 @@ class MatrixClient internal constructor( @@ -54,15 +56,28 @@ class MatrixClient internal constructor(
.addView(slidingSyncView)
.build()
private val slidingSyncObserverProxy = SlidingSyncObserverProxy(coroutineScope)
private val roomSummaryDataSource: RustRoomSummaryDataSource =
RustRoomSummaryDataSource(slidingSync, slidingSyncView, dispatchers)
RustRoomSummaryDataSource(slidingSyncObserverProxy.updateSummaryFlow, slidingSync, slidingSyncView, dispatchers)
private var slidingSyncObserverToken: StoppableSpawn? = null
init {
client.setDelegate(clientDelegate)
}
fun getRoom(roomId: String): MatrixRoom? {
val slidingSyncRoom = slidingSync.getRoom(roomId) ?: return null
val room = slidingSyncRoom.fullRoom() ?: return null
return MatrixRoom(
slidingSyncUpdateFlow = slidingSyncObserverProxy.updateSummaryFlow,
slidingSyncRoom = slidingSyncRoom,
room = room
)
}
fun startSync() {
slidingSync.setObserver(slidingSyncObserver)
roomSummaryDataSource.startSync()
slidingSync.setObserver(slidingSyncObserverProxy)
slidingSyncObserverToken = slidingSync.sync()
}
@ -113,7 +128,8 @@ class MatrixClient internal constructor( @@ -113,7 +128,8 @@ class MatrixClient internal constructor(
): Result<ByteArray> =
withContext(dispatchers.io) {
runCatching {
client.getMediaThumbnail(source, width.toULong(), height.toULong()).toUByteArray().toByteArray()
client.getMediaThumbnail(source, width.toULong(), height.toULong()).toUByteArray()
.toByteArray()
}
}

41
libraries/matrix/src/main/java/io/element/android/x/matrix/room/MatrixRoom.kt

@ -1,11 +1,50 @@ @@ -1,11 +1,50 @@
package io.element.android.x.matrix.room
import io.element.android.x.matrix.core.RoomId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.SlidingSyncRoom
import org.matrix.rustcomponents.sdk.UpdateSummary
class MatrixRoom(private val room: Room) {
class MatrixRoom(
private val slidingSyncUpdateFlow: Flow<UpdateSummary>,
private val slidingSyncRoom: SlidingSyncRoom,
private val room: Room,
) {
fun syncUpdateFlow(): Flow<Unit> {
return slidingSyncUpdateFlow
.filter {
it.rooms.contains(room.id())
}
.map { }
.onStart { emit(Unit) }
}
val roomId = RoomId(room.id())
val name: String?
get() {
return slidingSyncRoom.name()
}
val displayName: String
get() {
return room.displayName()
}
val topic: String?
get() {
return room.topic()
}
val avatarUrl: String?
get() {
return room.avatarUrl()
}
}

40
libraries/matrix/src/main/java/io/element/android/x/matrix/room/RoomSummaryDataSource.kt

@ -1,7 +1,6 @@ @@ -1,7 +1,6 @@
package io.element.android.x.matrix.room
import io.element.android.x.core.data.CoroutineDispatchers
import io.element.android.x.matrix.core.RoomId
import io.element.android.x.matrix.room.message.RoomMessageFactory
import io.element.android.x.matrix.sync.roomListDiff
import io.element.android.x.matrix.sync.state
@ -20,10 +19,11 @@ interface RoomSummaryDataSource { @@ -20,10 +19,11 @@ interface RoomSummaryDataSource {
}
internal class RustRoomSummaryDataSource(
private val slidingSyncUpdateFlow: Flow<UpdateSummary>,
private val slidingSync: SlidingSync,
private val slidingSyncView: SlidingSyncView,
private val coroutineDispatchers: CoroutineDispatchers,
private val roomMessageFactory: RoomMessageFactory = RoomMessageFactory(),
private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory()
) : RoomSummaryDataSource, Closeable {
private val coroutineScope = CoroutineScope(SupervisorJob() + coroutineDispatchers.io)
@ -31,7 +31,8 @@ internal class RustRoomSummaryDataSource( @@ -31,7 +31,8 @@ internal class RustRoomSummaryDataSource(
private val roomSummaries = MutableStateFlow<List<RoomSummary>>(emptyList())
private val state = MutableStateFlow(SlidingSyncState.COLD)
init {
fun startSync(){
slidingSyncView.roomListDiff()
.onEach { diff ->
updateRoomSummaries {
@ -44,6 +45,11 @@ internal class RustRoomSummaryDataSource( @@ -44,6 +45,11 @@ internal class RustRoomSummaryDataSource(
Timber.v("New sliding sync state: $slidingSyncState")
state.value = slidingSyncState
}.launchIn(coroutineScope)
slidingSyncUpdateFlow
.onEach {
didReceiveSyncUpdate(it)
}.launchIn(coroutineScope)
}
fun stopSync() {
@ -58,13 +64,13 @@ internal class RustRoomSummaryDataSource( @@ -58,13 +64,13 @@ internal class RustRoomSummaryDataSource(
return roomSummaries.sample(100)
}
internal fun updateRoomsWithIdentifiers(identifiers: List<String>) {
Timber.v("UpdateRooms with identifiers: $identifiers")
private fun didReceiveSyncUpdate(summary: UpdateSummary) {
Timber.v("UpdateRooms with identifiers: ${summary.rooms}")
if (state.value != SlidingSyncState.LIVE) {
return
}
updateRoomSummaries {
for (identifier in identifiers) {
for (identifier in summary.rooms) {
val index = indexOfFirst { it.identifier() == identifier }
if (index == -1) {
continue
@ -78,7 +84,7 @@ internal class RustRoomSummaryDataSource( @@ -78,7 +84,7 @@ internal class RustRoomSummaryDataSource(
private fun MutableList<RoomSummary>.applyDiff(diff: SlidingSyncViewRoomsListDiff) {
fun MutableList<RoomSummary>.fillUntil(untilIndex: Int) {
repeat((size-1 until untilIndex).count()) {
repeat((size - 1 until untilIndex).count()) {
add(buildEmptyRoomSummary())
}
}
@ -89,7 +95,7 @@ internal class RustRoomSummaryDataSource( @@ -89,7 +95,7 @@ internal class RustRoomSummaryDataSource(
add(roomSummary)
}
is SlidingSyncViewRoomsListDiff.UpdateAt -> {
fillUntil(diff.index.toInt())
//fillUntil(diff.index.toInt())
val roomSummary = buildSummaryForRoomListEntry(diff.value)
set(diff.index.toInt(), roomSummary)
}
@ -124,24 +130,8 @@ internal class RustRoomSummaryDataSource( @@ -124,24 +130,8 @@ internal class RustRoomSummaryDataSource(
private fun buildRoomSummaryForIdentifier(identifier: String): RoomSummary {
val room = slidingSync.getRoom(identifier) ?: return RoomSummary.Empty(identifier)
val latestRoomMessage = room.latestRoomMessage()?.let {
roomMessageFactory.create(it)
}
val computedLastMessage = when {
latestRoomMessage == null -> null
room.isDm() == true -> latestRoomMessage.body
else -> "${latestRoomMessage.sender.value}: ${latestRoomMessage.body}"
}
return RoomSummary.Filled(
details = RoomSummaryDetails(
roomId = RoomId(identifier),
name = room.name() ?: identifier,
isDirect = room.isDm() ?: false,
avatarURLString = room.fullRoom()?.avatarUrl(),
unreadNotificationCount = room.unreadNotifications().notificationCount().toInt(),
lastMessage = computedLastMessage,
lastMessageTimestamp = latestRoomMessage?.originServerTs
)
details = roomSummaryDetailsFactory.create(room, room.fullRoom())
)
}

30
libraries/matrix/src/main/java/io/element/android/x/matrix/room/RoomSummaryDetailsFactory.kt

@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
package io.element.android.x.matrix.room
import io.element.android.x.matrix.core.RoomId
import io.element.android.x.matrix.room.message.RoomMessageFactory
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.SlidingSyncRoom
class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFactory = RoomMessageFactory()) {
fun create(slidingSyncRoom: SlidingSyncRoom, room: Room?): RoomSummaryDetails{
val latestRoomMessage = slidingSyncRoom.latestRoomMessage()?.let {
roomMessageFactory.create(it)
}
val computedLastMessage = when {
latestRoomMessage == null -> null
slidingSyncRoom.isDm() == true -> latestRoomMessage.body
else -> "${latestRoomMessage.sender.value}: ${latestRoomMessage.body}"
}
return RoomSummaryDetails(
roomId = RoomId(slidingSyncRoom.roomId()),
name = slidingSyncRoom.name() ?: slidingSyncRoom.roomId(),
isDirect = slidingSyncRoom.isDm() ?: false,
avatarURLString = room?.avatarUrl(),
unreadNotificationCount = slidingSyncRoom.unreadNotifications().notificationCount().toInt(),
lastMessage = computedLastMessage,
lastMessageTimestamp = latestRoomMessage?.originServerTs
)
}
}

1
libraries/matrix/src/main/java/io/element/android/x/matrix/room/message/RoomMessageFactory.kt

@ -5,7 +5,6 @@ import io.element.android.x.matrix.core.UserId @@ -5,7 +5,6 @@ import io.element.android.x.matrix.core.UserId
import org.matrix.rustcomponents.sdk.EventTimelineItem
class RoomMessageFactory {
fun create(eventTimelineItem: EventTimelineItem?): RoomMessage? {
eventTimelineItem ?: return null
return RoomMessage(

20
libraries/matrix/src/main/java/io/element/android/x/matrix/sync/SlidingSyncObserverProxy.kt

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
package io.element.android.x.matrix.sync
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import org.matrix.rustcomponents.sdk.SlidingSyncObserver
import org.matrix.rustcomponents.sdk.UpdateSummary
class SlidingSyncObserverProxy(private val coroutineScope: CoroutineScope) : SlidingSyncObserver {
private val updateSummaryMutableFlow = MutableSharedFlow<UpdateSummary>()
val updateSummaryFlow: Flow<UpdateSummary> = updateSummaryMutableFlow
override fun didReceiveSyncUpdate(summary: UpdateSummary) {
coroutineScope.launch {
updateSummaryMutableFlow.emit(summary)
}
}
}
Loading…
Cancel
Save