Browse Source

Start implementing logic for room summary list

feature/bma/flipper
ganfra 2 years ago
parent
commit
a609433a50
  1. 6
      app/src/main/java/io/element/android/x/MainActivity.kt
  2. 30
      features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListScreen.kt
  3. 51
      features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt
  4. 4
      features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewState.kt
  5. 9
      libraries/core/src/main/java/io/element/android/x/core/data/CoroutineDispatchers.kt
  6. 7
      libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/Avatar.kt
  7. 1
      libraries/matrix/build.gradle
  8. 22
      libraries/matrix/src/main/java/io/element/android/x/matrix/Matrix.kt
  9. 133
      libraries/matrix/src/main/java/io/element/android/x/matrix/MatrixClient.kt
  10. 4
      libraries/matrix/src/main/java/io/element/android/x/matrix/core/EventId.kt
  11. 4
      libraries/matrix/src/main/java/io/element/android/x/matrix/core/RoomId.kt
  12. 4
      libraries/matrix/src/main/java/io/element/android/x/matrix/core/UserId.kt
  13. 11
      libraries/matrix/src/main/java/io/element/android/x/matrix/room/MatrixRoom.kt
  14. 26
      libraries/matrix/src/main/java/io/element/android/x/matrix/room/RoomSummary.kt
  15. 139
      libraries/matrix/src/main/java/io/element/android/x/matrix/room/RoomSummaryDataSource.kt
  16. 11
      libraries/matrix/src/main/java/io/element/android/x/matrix/room/message/RoomMessage.kt
  17. 19
      libraries/matrix/src/main/java/io/element/android/x/matrix/room/message/RoomMessageFactory.kt
  18. 4
      libraries/matrix/src/main/java/io/element/android/x/matrix/session/SessionStore.kt
  19. 32
      libraries/matrix/src/main/java/io/element/android/x/matrix/sync/SlidingSyncViewFlows.kt
  20. 15
      libraries/matrix/src/main/java/io/element/android/x/matrix/util/CallbackFlow.kt

6
app/src/main/java/io/element/android/x/MainActivity.kt

@ -1,10 +1,15 @@ @@ -1,10 +1,15 @@
@file:OptIn(ExperimentalMaterial3Api::class)
package io.element.android.x
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.ramcosta.composedestinations.DestinationsNavHost
import com.ramcosta.composedestinations.rememberNavHostEngine
import io.element.android.x.designsystem.ElementXTheme
@ -32,6 +37,7 @@ private fun MainScreen(viewModel: MainViewModel) { @@ -32,6 +37,7 @@ private fun MainScreen(viewModel: MainViewModel) {
val startRoute = runBlocking {
if (!viewModel.hasSession()) LoginScreenNavigationDestination else NavGraphs.root.startRoute
}
DestinationsNavHost(
engine = engine,
navController = navController,

30
features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListScreen.kt

@ -12,17 +12,19 @@ import androidx.compose.runtime.getValue @@ -12,17 +12,19 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.compose.collectAsState
import com.airbnb.mvrx.compose.mavericksViewModel
import io.element.android.x.ui.theme.components.Avatar
import org.matrix.rustcomponents.sdk.Room
import io.element.android.x.designsystem.components.Avatar
import io.element.android.x.matrix.core.RoomId
import io.element.android.x.matrix.room.RoomSummary
@Composable
fun RoomListScreen(
viewModel: RoomListViewModel = mavericksViewModel(),
onRoomClicked: (String) -> Unit = { },
onLogoutClicked: () -> Unit = { },
onRoomClicked: (RoomId) -> Unit = { }
) {
val state by viewModel.collectAsState()
RoomListContent(state, onRoomClicked, onLogoutClicked)
@ -31,8 +33,8 @@ fun RoomListScreen( @@ -31,8 +33,8 @@ fun RoomListScreen(
@Composable
fun RoomListContent(
state: RoomListViewState,
onRoomClicked: (String) -> Unit,
onLogoutClicked: () -> Unit
onRoomClicked: (RoomId) -> Unit,
onLogoutClicked: () -> Unit,
) {
Surface(color = MaterialTheme.colorScheme.background) {
Column(
@ -62,7 +64,8 @@ fun RoomListTopBar(state: RoomListViewState, onLogoutClicked: () -> Unit) { @@ -62,7 +64,8 @@ fun RoomListTopBar(state: RoomListViewState, onLogoutClicked: () -> Unit) {
TopAppBar(
title = {
Row(
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
val matrixUser = state.user
Avatar(data = matrixUser.avatarData)
@ -83,19 +86,24 @@ fun RoomListTopBar(state: RoomListViewState, onLogoutClicked: () -> Unit) { @@ -83,19 +86,24 @@ fun RoomListTopBar(state: RoomListViewState, onLogoutClicked: () -> Unit) {
@Composable
private fun RoomItem(
modifier: Modifier = Modifier,
room: Room,
onClick: (String) -> Unit
room: RoomSummary,
onClick: (RoomId) -> Unit
) {
if (room !is RoomSummary.Filled) {
return
}
val details = room.details
Row(verticalAlignment = Alignment.CenterVertically,
modifier = modifier
.clickable {
onClick(room.id())
onClick(room.details.roomId)
}
.fillMaxWidth()
.padding(horizontal = 8.dp)
) {
Column(modifier = modifier.padding(8.dp)) {
Text(text = "Room: ${room.name() ?: room.id()}")
Text(text = if (room.isDirect()) "Direct" else "Room")
Text(fontSize = 18.sp, text = details.name.orEmpty())
Text(text = details.lastMessage?.toString().orEmpty(), maxLines = 2)
}
}
}

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

@ -4,19 +4,14 @@ import com.airbnb.mvrx.Fail @@ -4,19 +4,14 @@ 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.tryOrNull
import io.element.android.x.matrix.MatrixClient
import io.element.android.x.matrix.MatrixInstance
import kotlinx.coroutines.launch
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.StoppableSpawn
import org.matrix.rustcomponents.sdk.UpdateSummary
import org.matrix.rustcomponents.sdk.mediaSourceFromUrl
class RoomListViewModel(initialState: RoomListViewState) :
MavericksViewModel<RoomListViewState>(initialState), MatrixClient.SlidingSyncListener {
MavericksViewModel<RoomListViewState>(initialState) {
private var sync: StoppableSpawn? = null
private val matrix = MatrixInstance.getInstance()
init {
@ -33,18 +28,26 @@ class RoomListViewModel(initialState: RoomListViewState) : @@ -33,18 +28,26 @@ class RoomListViewModel(initialState: RoomListViewState) :
private fun handleInit() {
viewModelScope.launch {
val client = getClient()
val url = client.avatarUrl()
val mediaSource = mediaSourceFromUrl(url)
client.startSync()
val userAvatarUrl = client.loadUserAvatarURLString().getOrNull()
val userDisplayName = client.loadUserDisplayName().getOrNull()
val avatarData = userAvatarUrl?.let {
mediaSourceFromUrl(it)
}?.let {
client.loadMediaContentForSource(it)
}
setState {
copy(
user = MatrixUser(
username = tryOrNull { client.username() } ?: "Room list",
avatarUrl = mediaSource.url(),
avatarData = client.loadMedia2(url)
username = userDisplayName,
avatarUrl = userAvatarUrl,
avatarData = avatarData?.getOrNull()
)
)
}
sync = client.slidingSync(listener = this@RoomListViewModel)
client.roomSummaryDataSource().roomSummaries().execute {
copy(rooms = it)
}
}
}
@ -64,31 +67,7 @@ class RoomListViewModel(initialState: RoomListViewState) : @@ -64,31 +67,7 @@ class RoomListViewModel(initialState: RoomListViewState) :
return matrix.restoreSession()!!
}
override fun onSyncUpdate(
summary: UpdateSummary,
rooms: List<Room>
) = withState { state ->
val list = state.rooms().orEmpty().toMutableList()
rooms.forEach { room ->
// Either replace or add the room
val idx = list.indexOfFirst { it.id() == room.id() }
if (idx == -1) {
list.add(room)
} else {
list[idx] = room
}
}
setState {
copy(
rooms = Success(list),
summary = Success(summary)
)
}
}
override fun onCleared() {
super.onCleared()
sync?.cancel()
}
}

4
features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewState.kt

@ -3,13 +3,13 @@ package io.element.android.x.features.roomlist @@ -3,13 +3,13 @@ package io.element.android.x.features.roomlist
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.Uninitialized
import io.element.android.x.matrix.room.RoomSummary
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.UpdateSummary
data class RoomListViewState(
val user: MatrixUser = MatrixUser(),
val rooms: Async<List<Room>> = Uninitialized,
val summary: Async<UpdateSummary> = Uninitialized,
val rooms: Async<List<RoomSummary>> = Uninitialized,
val canLoadMore: Boolean = false,
val logoutAction: Async<Unit> = Uninitialized,
) : MavericksState

9
libraries/core/src/main/java/io/element/android/x/core/data/CoroutineDispatchers.kt

@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
package io.element.android.x.core.data
import kotlinx.coroutines.CoroutineDispatcher
data class CoroutineDispatchers(
val io: CoroutineDispatcher,
val computation: CoroutineDispatcher,
val main: CoroutineDispatcher,
)

7
libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/Avatar.kt

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package io.element.android.x.ui.theme.components
package io.element.android.x.designsystem.components
import android.util.Log
import androidx.compose.foundation.Image
@ -9,6 +9,7 @@ import androidx.compose.material3.MaterialTheme @@ -9,6 +9,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import coil.compose.rememberAsyncImagePainter
@ -18,7 +19,7 @@ import coil.compose.rememberAsyncImagePainter @@ -18,7 +19,7 @@ import coil.compose.rememberAsyncImagePainter
@Composable
fun Avatar(
data: List<UByte>?,
size: Int = 48,
size: Dp = 48.dp,
) {
Image(
painter = rememberAsyncImagePainter(
@ -28,7 +29,7 @@ fun Avatar( @@ -28,7 +29,7 @@ fun Avatar(
}),
contentDescription = null,
modifier = Modifier
.size(size.dp)
.size(size)
.clip(CircleShape)
.border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
)

1
libraries/matrix/build.gradle

@ -29,6 +29,7 @@ android { @@ -29,6 +29,7 @@ android {
dependencies {
api(name: 'matrix-rust-sdk', ext: 'aar')
implementation project(":libraries:core")
implementation "net.java.dev.jna:jna:5.10.0@aar"
implementation 'androidx.datastore:datastore-core:1.0.0'
implementation 'androidx.datastore:datastore-preferences:1.0.0'

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

@ -1,8 +1,10 @@ @@ -1,8 +1,10 @@
package io.element.android.x.matrix
import android.content.Context
import io.element.android.x.matrix.store.SessionStore
import io.element.android.x.core.data.CoroutineDispatchers
import io.element.android.x.matrix.session.SessionStore
import io.element.android.x.matrix.util.logError
import kotlinx.coroutines.Dispatchers
import org.matrix.rustcomponents.sdk.AuthenticationService
import org.matrix.rustcomponents.sdk.ClientBuilder
import java.io.File
@ -10,6 +12,12 @@ import java.io.File @@ -10,6 +12,12 @@ import java.io.File
class Matrix(
context: Context,
) {
private val coroutineDispatchers = CoroutineDispatchers(
io = Dispatchers.IO,
computation = Dispatchers.Default,
main = Dispatchers.Main
)
private val baseFolder = File(context.filesDir, "matrix")
private val sessionStore = SessionStore(context)
@ -17,18 +25,18 @@ class Matrix( @@ -17,18 +25,18 @@ class Matrix(
return sessionStore.getStoredData()
?.let { sessionData ->
try {
val client = ClientBuilder()
ClientBuilder()
.basePath(baseFolder.absolutePath)
.username(sessionData.userId)
.build()
client.restoreLogin(sessionData.restoreToken)
client
.build().apply {
restoreLogin(sessionData.restoreToken)
}
} catch (throwable: Throwable) {
logError(throwable)
null
}
}?.let {
MatrixClient(it, sessionStore)
MatrixClient(it, sessionStore, coroutineDispatchers)
}
}
@ -37,6 +45,6 @@ class Matrix( @@ -37,6 +45,6 @@ class Matrix(
authService.configureHomeserver(homeserver)
val client = authService.login(username, password, "MatrixRustSDKSample", null)
sessionStore.storeData(SessionStore.SessionData(client.userId(), client.restoreToken()))
return MatrixClient(client, sessionStore)
return MatrixClient(client, sessionStore, coroutineDispatchers)
}
}

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

@ -1,78 +1,105 @@ @@ -1,78 +1,105 @@
package io.element.android.x.matrix
import android.util.Log
import io.element.android.x.matrix.store.SessionStore
import io.element.android.x.core.data.CoroutineDispatchers
import io.element.android.x.matrix.core.UserId
import io.element.android.x.matrix.room.RoomSummaryDataSource
import io.element.android.x.matrix.room.RustRoomSummaryDataSource
import io.element.android.x.matrix.session.SessionStore
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.*
import java.io.Closeable
class MatrixClient internal constructor(
private val client: Client,
private val sessionStore: SessionStore,
) {
private val roomWrapper = RoomWrapper(client)
private val dispatchers: CoroutineDispatchers,
) : Closeable {
fun startSync() {
val clientDelegate = object : ClientDelegate {
override fun didReceiveAuthError(isSoftLogout: Boolean) {
Log.v(LOG_TAG, "didReceiveAuthError()")
}
private val clientDelegate = object : ClientDelegate {
override fun didReceiveAuthError(isSoftLogout: Boolean) {
Log.v(LOG_TAG, "didReceiveAuthError()")
}
override fun didReceiveSyncUpdate() {
Log.v(LOG_TAG, "didReceiveSyncUpdate()")
}
override fun didReceiveSyncUpdate() {
Log.v(LOG_TAG, "didReceiveSyncUpdate()")
}
override fun didUpdateRestoreToken() {
Log.v(LOG_TAG, "didUpdateRestoreToken()")
}
override fun didUpdateRestoreToken() {
Log.v(LOG_TAG, "didUpdateRestoreToken()")
}
}
client.setDelegate(clientDelegate)
Log.v(LOG_TAG, "DisplayName = ${client.displayName()}")
try {
client.fullSlidingSync()
} catch (failure: Throwable) {
Log.e(LOG_TAG, "fullSlidingSync() fail", failure)
private val slidingSyncObserver = object : SlidingSyncObserver {
override fun didReceiveSyncUpdate(summary: UpdateSummary) {
Log.v(LOG_TAG, "didReceiveSyncUpdate=$summary")
roomSummaryDataSource.updateRoomsWithIdentifiers(summary.rooms)
}
}
fun slidingSync(listener: SlidingSyncListener): StoppableSpawn {
val slidingSyncView = SlidingSyncViewBuilder()
.timelineLimit(limit = 10u)
.requiredState(requiredState = listOf(RequiredState(key = "m.room.avatar", value = "")))
.name(name = "HomeScreenView")
.syncMode(mode = SlidingSyncMode.FULL_SYNC)
.build()
val slidingSync = client
.slidingSync()
.homeserver("https://slidingsync.lab.element.dev")
.addView(slidingSyncView)
.build()
slidingSync.setObserver(object : SlidingSyncObserver {
override fun didReceiveSyncUpdate(summary: UpdateSummary) {
Log.v(LOG_TAG, "didReceiveSyncUpdate=$summary")
val rooms = summary.rooms.mapNotNull {
roomWrapper.getRoom(it)
}
listener.onSyncUpdate(summary, rooms)
}
})
return slidingSync.sync()
private val slidingSyncView = SlidingSyncViewBuilder()
.timelineLimit(limit = 10u)
.requiredState(requiredState = listOf(RequiredState(key = "m.room.avatar", value = "")))
.name(name = "HomeScreenView")
.syncMode(mode = SlidingSyncMode.FULL_SYNC)
.build()
private val slidingSync = client
.slidingSync()
.homeserver("https://slidingsync.lab.element.dev")
.addView(slidingSyncView)
.build()
private val roomSummaryDataSource: RustRoomSummaryDataSource =
RustRoomSummaryDataSource(slidingSync, slidingSyncView, dispatchers)
private var slidingSyncObserverToken: StoppableSpawn? = null
init {
client.setDelegate(clientDelegate)
}
fun startSync() {
slidingSync.setObserver(slidingSyncObserver)
slidingSyncObserverToken = slidingSync.sync()
}
fun stopSync() {
slidingSync.setObserver(null)
slidingSyncObserverToken?.cancel()
}
fun roomSummaryDataSource(): RoomSummaryDataSource = roomSummaryDataSource
override fun close() {
stopSync()
client.setDelegate(null)
}
suspend fun logout() {
suspend fun logout() = withContext(dispatchers.io) {
close()
client.logout()
sessionStore.reset()
}
fun userId(): String = client.userId()
fun username(): String = client.displayName()
fun avatarUrl(): String = client.avatarUrl()
fun loadMedia(source: MediaSource) = client.getMediaContent(source)
fun loadMedia2(mxcUrl: String) = client.getMediaContent(mediaSourceFromUrl(mxcUrl))
fun userId(): UserId = UserId(client.userId())
suspend fun loadUserDisplayName(): Result<String> = withContext(dispatchers.io) {
runCatching {
client.displayName()
}
}
interface SlidingSyncListener {
fun onSyncUpdate(summary: UpdateSummary, rooms: List<Room>)
suspend fun loadUserAvatarURLString(): Result<String> = withContext(dispatchers.io) {
runCatching {
client.avatarUrl()
}
}
suspend fun loadMediaContentForSource(source: MediaSource): Result<List<UByte>> =
withContext(dispatchers.io) {
runCatching {
client.getMediaContent(source)
}
}
}

4
libraries/matrix/src/main/java/io/element/android/x/matrix/core/EventId.kt

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
package io.element.android.x.matrix.core
@JvmInline
value class EventId(val value: String)

4
libraries/matrix/src/main/java/io/element/android/x/matrix/core/RoomId.kt

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
package io.element.android.x.matrix.core
@JvmInline
value class RoomId(val value: String)

4
libraries/matrix/src/main/java/io/element/android/x/matrix/core/UserId.kt

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
package io.element.android.x.matrix.core
@JvmInline
value class UserId(val value: String)

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

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
package io.element.android.x.matrix.room
import io.element.android.x.matrix.core.RoomId
import org.matrix.rustcomponents.sdk.Room
class MatrixRoom(private val room: Room) {
val roomId = RoomId(room.id())
}

26
libraries/matrix/src/main/java/io/element/android/x/matrix/room/RoomSummary.kt

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
package io.element.android.x.matrix.room
import io.element.android.x.matrix.core.RoomId
sealed interface RoomSummary {
data class Empty(val identifier: String) : RoomSummary
data class Filled(val details: RoomSummaryDetails) : RoomSummary
fun identifier(): String {
return when (this) {
is Empty -> identifier
is Filled -> details.roomId.value
}
}
}
data class RoomSummaryDetails(
val roomId: RoomId,
val name: String?,
val isDirect: Boolean,
val avatarURLString: String?,
val lastMessage: CharSequence?,
val lastMessageTimestamp: Long?,
val unreadNotificationCount: UInt,
)

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

@ -0,0 +1,139 @@ @@ -0,0 +1,139 @@
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
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.matrix.rustcomponents.sdk.*
import java.util.*
interface RoomSummaryDataSource {
fun roomSummaries(): Flow<List<RoomSummary>>
}
internal class RustRoomSummaryDataSource(
private val slidingSync: SlidingSync,
private val slidingSyncView: SlidingSyncView,
private val coroutineDispatchers: CoroutineDispatchers,
private val roomMessageFactory: RoomMessageFactory = RoomMessageFactory(),
) : RoomSummaryDataSource {
private val coroutineScope = CoroutineScope(SupervisorJob() + coroutineDispatchers.io)
private val roomSummaries = MutableStateFlow<List<RoomSummary>>(emptyList())
private val state = MutableStateFlow(SlidingSyncState.COLD)
init {
slidingSyncView.roomListDiff()
.onEach { diff ->
updateRoomSummaries {
applyDiff(diff)
}
}.launchIn(coroutineScope)
slidingSyncView.state()
.onEach { newRoomState ->
state.value = newRoomState
}.launchIn(coroutineScope)
}
override fun roomSummaries(): Flow<List<RoomSummary>> {
return roomSummaries
}
internal fun updateRoomsWithIdentifiers(identifiers: List<String>) {
if (state.value != SlidingSyncState.LIVE) {
return
}
val roomSummaryList = roomSummaries.value.toMutableList()
for (identifier in identifiers) {
val index = roomSummaryList.indexOfFirst { it.identifier() == identifier }
if (index == -1) {
continue
}
val updatedRoomSummary = buildRoomSummaryForIdentifier(identifier)
roomSummaryList[index] = updatedRoomSummary
}
roomSummaries.value = roomSummaryList
}
private fun MutableList<RoomSummary>.applyDiff(diff: SlidingSyncViewRoomsListDiff) {
if (diff.isInvalidation()) {
return
}
when (diff) {
is SlidingSyncViewRoomsListDiff.Push -> {
val roomSummary = buildSummaryForRoomListEntry(diff.value)
add(roomSummary)
}
is SlidingSyncViewRoomsListDiff.UpdateAt -> {
val roomSummary = buildSummaryForRoomListEntry(diff.value)
set(diff.index.toInt(), roomSummary)
}
is SlidingSyncViewRoomsListDiff.InsertAt -> {
val roomSummary = buildSummaryForRoomListEntry(diff.value)
add(diff.index.toInt(), roomSummary)
}
is SlidingSyncViewRoomsListDiff.Move -> {
Collections.swap(this, diff.oldIndex.toInt(), diff.newIndex.toInt())
}
is SlidingSyncViewRoomsListDiff.RemoveAt -> {
removeAt(diff.index.toInt())
}
is SlidingSyncViewRoomsListDiff.Replace -> {
clear()
addAll(diff.values.map { buildSummaryForRoomListEntry(it) })
}
}
}
private fun buildSummaryForRoomListEntry(entry: RoomListEntry): RoomSummary {
return when (entry) {
RoomListEntry.Empty -> RoomSummary.Empty(UUID.randomUUID().toString())
is RoomListEntry.Invalidated -> buildRoomSummaryForIdentifier(entry.roomId)
is RoomListEntry.Filled -> buildRoomSummaryForIdentifier(entry.roomId)
}
}
private fun buildRoomSummaryForIdentifier(identifier: String): RoomSummary {
val room = slidingSync.getRoom(identifier) ?: return RoomSummary.Empty(identifier)
val latestRoomMessage = room.latestRoomMessage()?.let {
roomMessageFactory.create(it)
}
return RoomSummary.Filled(
details = RoomSummaryDetails(
roomId = RoomId(identifier),
name = room.name(),
isDirect = room.isDm() ?: false,
avatarURLString = room.fullRoom()?.avatarUrl(),
unreadNotificationCount = room.unreadNotifications().notificationCount(),
lastMessage = latestRoomMessage?.body,
lastMessageTimestamp = latestRoomMessage?.originServerTs
)
)
}
private fun updateRoomSummaries(block: MutableList<RoomSummary>.() -> Unit) {
val mutableRoomSummaries = roomSummaries.value.toMutableList()
block(mutableRoomSummaries)
roomSummaries.value = mutableRoomSummaries
}
}
fun SlidingSyncViewRoomsListDiff.isInvalidation(): Boolean {
return when (this) {
is SlidingSyncViewRoomsListDiff.InsertAt -> this.value is RoomListEntry.Invalidated
is SlidingSyncViewRoomsListDiff.UpdateAt -> this.value is RoomListEntry.Invalidated
is SlidingSyncViewRoomsListDiff.Push -> this.value is RoomListEntry.Invalidated
else -> false
}
}

11
libraries/matrix/src/main/java/io/element/android/x/matrix/room/message/RoomMessage.kt

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
package io.element.android.x.matrix.room.message
import io.element.android.x.matrix.core.EventId
import io.element.android.x.matrix.core.UserId
data class RoomMessage(
val eventId: EventId,
val body: String,
val sender: UserId,
val originServerTs: Long,
)

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

@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
package io.element.android.x.matrix.room.message
import io.element.android.x.matrix.core.EventId
import io.element.android.x.matrix.core.UserId
import org.matrix.rustcomponents.sdk.AnyMessage
class RoomMessageFactory {
fun create(anyMessage: AnyMessage): RoomMessage? {
val textMessage = anyMessage.textMessage()?.baseMessage() ?: return null
return RoomMessage(
eventId = EventId(textMessage.id()),
body = textMessage.body(),
sender = UserId(textMessage.sender()),
originServerTs = textMessage.originServerTs().toLong()
)
}
}

4
libraries/matrix/src/main/java/io/element/android/x/matrix/store/SessionStore.kt → libraries/matrix/src/main/java/io/element/android/x/matrix/session/SessionStore.kt

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package io.element.android.x.matrix.store
package io.element.android.x.matrix.session
import android.content.Context
import androidx.datastore.core.DataStore
@ -8,13 +8,13 @@ import androidx.datastore.preferences.core.stringPreferencesKey @@ -8,13 +8,13 @@ import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.firstOrNull
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "elementx_sessions")
private val userIdPreference = stringPreferencesKey("userId")
// TODO It contains the access token, so it has to be stored in a more secured storage.
// I would expect the Rust SDK to provide a more obscure token.
private val restoreTokenPreference = stringPreferencesKey("restoreToken")
internal class SessionStore(
context: Context
) {

32
libraries/matrix/src/main/java/io/element/android/x/matrix/sync/SlidingSyncViewFlows.kt

@ -0,0 +1,32 @@ @@ -0,0 +1,32 @@
package io.element.android.x.matrix.sync
import kotlinx.coroutines.flow.Flow
import mxCallbackFlow
import org.matrix.rustcomponents.sdk.*
fun SlidingSyncView.roomListDiff(): Flow<SlidingSyncViewRoomsListDiff> = mxCallbackFlow {
val observer = object : SlidingSyncViewRoomListObserver {
override fun didReceiveUpdate(diff: SlidingSyncViewRoomsListDiff) {
trySend(diff)
}
}
observeRoomList(observer)
}
fun SlidingSyncView.state(): Flow<SlidingSyncState> = mxCallbackFlow {
val observer = object : SlidingSyncViewStateObserver {
override fun didReceiveUpdate(newState: SlidingSyncState) {
trySend(newState)
}
}
observeState(observer)
}
fun SlidingSyncView.roomsCount(): Flow<UInt> = mxCallbackFlow {
val observer = object : SlidingSyncViewRoomsCountObserver {
override fun didReceiveUpdate(count: UInt) {
trySend(count)
}
}
observeRoomsCount(observer)
}

15
libraries/matrix/src/main/java/io/element/android/x/matrix/util/CallbackFlow.kt

@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
@file:OptIn(ExperimentalCoroutinesApi::class)
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.ProducerScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
import org.matrix.rustcomponents.sdk.StoppableSpawn
internal fun <T> mxCallbackFlow(block: suspend ProducerScope<T>.() -> StoppableSpawn) =
callbackFlow {
val token: StoppableSpawn = block(this)
awaitClose {
token.cancel()
}
}
Loading…
Cancel
Save