From 501dd176e55e7e32122782533bcfec322ba5b7de Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 12 Oct 2022 15:38:11 +0200 Subject: [PATCH] First mapping of room. Still very dirty --- .../io/element/android/x/core/data/Try.kt | 14 +++++ libraries/sdk/matrix/build.gradle | 2 +- .../android/x/sdk/matrix/MatrixClient.kt | 16 +++++- .../android/x/sdk/matrix/RoomWrapper.kt | 15 ++++++ .../x/ui/screen/login/LoginActivity.kt | 35 +++++++++---- libraries/ui/screens/roomlist/build.gradle | 1 + .../x/ui/screen/roomlist/MatrixUser.kt | 6 +++ .../x/ui/screen/roomlist/RoomListActions.kt | 3 +- .../x/ui/screen/roomlist/RoomListActivity.kt | 46 ++++++++++++----- .../x/ui/screen/roomlist/RoomListViewModel.kt | 51 ++++++++++++++++++- .../x/ui/screen/roomlist/RoomListViewState.kt | 6 ++- 11 files changed, 165 insertions(+), 30 deletions(-) create mode 100644 libraries/core/src/main/java/io/element/android/x/core/data/Try.kt create mode 100644 libraries/sdk/matrix/src/main/java/io/element/android/x/sdk/matrix/RoomWrapper.kt create mode 100644 libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/roomlist/MatrixUser.kt diff --git a/libraries/core/src/main/java/io/element/android/x/core/data/Try.kt b/libraries/core/src/main/java/io/element/android/x/core/data/Try.kt new file mode 100644 index 0000000000..6c3cfa8599 --- /dev/null +++ b/libraries/core/src/main/java/io/element/android/x/core/data/Try.kt @@ -0,0 +1,14 @@ +package io.element.android.x.core.data + +import android.util.Log + +inline fun tryOrNull(message: String? = null, operation: () -> A): A? { + return try { + operation() + } catch (any: Throwable) { + if (message != null) { + Log.e("TAG", message, any) + } + null + } +} diff --git a/libraries/sdk/matrix/build.gradle b/libraries/sdk/matrix/build.gradle index 0e463642ac..abe22c6b00 100644 --- a/libraries/sdk/matrix/build.gradle +++ b/libraries/sdk/matrix/build.gradle @@ -28,7 +28,7 @@ android { } dependencies { - implementation(name: 'matrix-rust-sdk', ext: 'aar') + api(name: 'matrix-rust-sdk', ext: 'aar') 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' diff --git a/libraries/sdk/matrix/src/main/java/io/element/android/x/sdk/matrix/MatrixClient.kt b/libraries/sdk/matrix/src/main/java/io/element/android/x/sdk/matrix/MatrixClient.kt index c36fab6b48..d1528d363f 100644 --- a/libraries/sdk/matrix/src/main/java/io/element/android/x/sdk/matrix/MatrixClient.kt +++ b/libraries/sdk/matrix/src/main/java/io/element/android/x/sdk/matrix/MatrixClient.kt @@ -8,6 +8,8 @@ class MatrixClient internal constructor( private val client: Client, private val sessionStore: SessionStore, ) { + private val roomWrapper = RoomWrapper(client) + fun startSync() { val clientDelegate = object : ClientDelegate { override fun didReceiveAuthError(isSoftLogout: Boolean) { @@ -32,7 +34,7 @@ class MatrixClient internal constructor( } } - fun slidingSync(onSyncUpdate: (UpdateSummary) -> Unit): StoppableSpawn { + fun slidingSync(listener: SlidingSyncListener): StoppableSpawn { val slidingSyncView = SlidingSyncViewBuilder() .timelineLimit(limit = 10u) .requiredState(requiredState = listOf(RequiredState(key = "m.room.avatar", value = ""))) @@ -49,7 +51,10 @@ class MatrixClient internal constructor( slidingSync.setObserver(object : SlidingSyncObserver { override fun didReceiveSyncUpdate(summary: UpdateSummary) { Log.v(LOG_TAG, "didReceiveSyncUpdate=$summary") - onSyncUpdate.invoke(summary) + val rooms = summary.rooms.mapNotNull { + roomWrapper.getRoom(it) + } + listener.onSyncUpdate(summary, rooms) } }) return slidingSync.sync() @@ -59,4 +64,11 @@ class MatrixClient internal constructor( client.logout() sessionStore.reset() } + + fun username(): String = client.displayName() + fun avatarUrl(): String = client.avatarUrl() + + interface SlidingSyncListener { + fun onSyncUpdate(summary: UpdateSummary, rooms: List) + } } diff --git a/libraries/sdk/matrix/src/main/java/io/element/android/x/sdk/matrix/RoomWrapper.kt b/libraries/sdk/matrix/src/main/java/io/element/android/x/sdk/matrix/RoomWrapper.kt new file mode 100644 index 0000000000..bf20258a4c --- /dev/null +++ b/libraries/sdk/matrix/src/main/java/io/element/android/x/sdk/matrix/RoomWrapper.kt @@ -0,0 +1,15 @@ +package io.element.android.x.sdk.matrix + +import android.util.Log +import org.matrix.rustcomponents.sdk.Client +import org.matrix.rustcomponents.sdk.Room + +class RoomWrapper( + private val client: Client +) { + fun getRoom(roomId: String): Room? { + val rooms = client.rooms() + Log.d(LOG_TAG, "We have ${rooms.size} rooms") + return rooms.firstOrNull { it.id() == roomId } + } +} \ No newline at end of file diff --git a/libraries/ui/screens/login/src/main/java/io/element/android/x/ui/screen/login/LoginActivity.kt b/libraries/ui/screens/login/src/main/java/io/element/android/x/ui/screen/login/LoginActivity.kt index 919db9c71c..68dbd3aee9 100644 --- a/libraries/ui/screens/login/src/main/java/io/element/android/x/ui/screen/login/LoginActivity.kt +++ b/libraries/ui/screens/login/src/main/java/io/element/android/x/ui/screen/login/LoginActivity.kt @@ -7,13 +7,13 @@ import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.* import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading @@ -22,10 +22,10 @@ import com.airbnb.mvrx.compose.collectAsState import com.airbnb.mvrx.compose.mavericksViewModel import io.element.android.x.ui.theme.ElementXTheme import io.element.android.x.ui.theme.components.VectorButton -import io.element.android.x.ui.theme.components.VectorTextField class LoginActivity : ComponentActivity() { + @OptIn(ExperimentalMaterial3Api::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -44,21 +44,34 @@ class LoginActivity : ComponentActivity() { val viewModel: LoginViewModel = mavericksViewModel() val state by viewModel.collectAsState() val isError = state.isLoggedIn is Fail - VectorTextField(value = state.homeserver, + OutlinedTextField( + value = state.homeserver, onValueChange = { viewModel.handle(LoginActions.SetHomeserver(it)) - }) - VectorTextField( + }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Uri, + ), + ) + OutlinedTextField( value = state.login, onValueChange = { viewModel.handle(LoginActions.SetLogin(it)) - }) - VectorTextField( + }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Text, + ), + ) + OutlinedTextField( value = state.password, onValueChange = { viewModel.handle(LoginActions.SetPassword(it)) }, - isError = isError + isError = isError, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Password, + imeAction = ImeAction.Send, + ), ) if (isError) { Text( diff --git a/libraries/ui/screens/roomlist/build.gradle b/libraries/ui/screens/roomlist/build.gradle index 3b9f08b932..98641a9681 100644 --- a/libraries/ui/screens/roomlist/build.gradle +++ b/libraries/ui/screens/roomlist/build.gradle @@ -58,4 +58,5 @@ dependencies { implementation 'androidx.fragment:fragment-ktx:1.5.3' implementation 'com.airbnb.android:mavericks-compose:2.7.0' + implementation 'io.coil-kt:coil-compose:2.2.1' } \ No newline at end of file diff --git a/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/roomlist/MatrixUser.kt b/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/roomlist/MatrixUser.kt new file mode 100644 index 0000000000..aef8b72151 --- /dev/null +++ b/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/roomlist/MatrixUser.kt @@ -0,0 +1,6 @@ +package io.element.android.x.ui.screen.roomlist + +data class MatrixUser( + val username: String? = null, + val avatarUrl: String? = null, +) diff --git a/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/roomlist/RoomListActions.kt b/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/roomlist/RoomListActions.kt index e5bf667814..d8f08220f6 100644 --- a/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/roomlist/RoomListActions.kt +++ b/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/roomlist/RoomListActions.kt @@ -1,6 +1,7 @@ package io.element.android.x.ui.screen.roomlist sealed interface RoomListActions { - object Logout : RoomListActions + object Init : RoomListActions object LoadMore : RoomListActions + object Logout : RoomListActions } diff --git a/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/roomlist/RoomListActivity.kt b/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/roomlist/RoomListActivity.kt index b6ee71bcde..033bd8c96e 100644 --- a/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/roomlist/RoomListActivity.kt +++ b/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/roomlist/RoomListActivity.kt @@ -3,13 +3,15 @@ package io.element.android.x.ui.screen.roomlist import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ExitToApp import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import coil.compose.rememberAsyncImagePainter import com.airbnb.mvrx.Success import com.airbnb.mvrx.compose.collectAsState import com.airbnb.mvrx.compose.mavericksViewModel @@ -17,12 +19,18 @@ import io.element.android.x.ui.theme.ElementXTheme class RoomListActivity : ComponentActivity() { + private var initDone = false + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { ElementXTheme { val viewModel: RoomListViewModel = mavericksViewModel() + if (!initDone) { + initDone = true + viewModel.handle(RoomListActions.Init) + } val state = viewModel.collectAsState() // A surface container using the 'background' color from the theme Surface( @@ -34,12 +42,13 @@ class RoomListActivity : ComponentActivity() { Column( modifier = Modifier.fillMaxSize() ) { - OptionMenu(viewModel) - /* TODO - val state = viewModel.state.collectAsState().value - RoomListHeader() - RoomList() - */ + OptionMenu(state.value.user, viewModel) + val rooms = state.value.rooms + if (rooms is Success) { + rooms().forEach { + Text(text = "Room: ${it.name() ?: it.id()}") + } + } } } if (state.value.logoutAction is Success) { @@ -51,14 +60,25 @@ class RoomListActivity : ComponentActivity() { @OptIn(ExperimentalMaterial3Api::class) @Composable - private fun OptionMenu(viewModel: RoomListViewModel) { + private fun OptionMenu(matrixUser: MatrixUser, viewModel: RoomListViewModel) { TopAppBar( - title = { Text("Room List") }, + title = { + Row( + modifier = Modifier.fillMaxWidth() + ) { + Image( + painter = rememberAsyncImagePainter(matrixUser.avatarUrl), + contentDescription = null, + modifier = Modifier.size(48.dp) + ) + Text("${matrixUser.username}") + } + }, actions = { - Button( + IconButton( onClick = { viewModel.handle(RoomListActions.Logout) } ) { - Text(text = "logout") + Icon(Icons.Default.ExitToApp, contentDescription = "logout") } } ) diff --git a/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/roomlist/RoomListViewModel.kt b/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/roomlist/RoomListViewModel.kt index 278502f0a7..4f473103dc 100644 --- a/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/roomlist/RoomListViewModel.kt +++ b/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/roomlist/RoomListViewModel.kt @@ -4,22 +4,43 @@ 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.sdk.matrix.MatrixClient import io.element.android.x.sdk.matrix.MatrixInstance import kotlinx.coroutines.launch +import org.matrix.rustcomponents.sdk.Room +import org.matrix.rustcomponents.sdk.StoppableSpawn +import org.matrix.rustcomponents.sdk.UpdateSummary class RoomListViewModel(initialState: RoomListViewState) : - MavericksViewModel(initialState) { + MavericksViewModel(initialState), MatrixClient.SlidingSyncListener { + private var sync: StoppableSpawn? = null private val matrix = MatrixInstance.getInstance() fun handle(action: RoomListActions) { when (action) { + RoomListActions.Init -> handleInit() RoomListActions.LoadMore -> TODO() RoomListActions.Logout -> handleLogout() } } + private fun handleInit() { + viewModelScope.launch { + val client = getClient() + setState { + copy( + user = MatrixUser( + tryOrNull { client.username() } ?: "Room list", + tryOrNull { client.avatarUrl() } ?: "https://previews.123rf.com/images/lkeskinen/lkeskinen1802/lkeskinen180208322/95731150-exemple-de-tampon-%C3%A9tiquette-typographique-timbre-ou-ic%C3%B4ne.jpg", + ) + ) + } + sync = client.slidingSync(listener = this@RoomListViewModel) + } + } + private fun handleLogout() { viewModelScope.launch { setState { copy(logoutAction = Loading()) } @@ -35,4 +56,32 @@ class RoomListViewModel(initialState: RoomListViewState) : private suspend fun getClient(): MatrixClient { return matrix.restoreSession()!! } + + override fun onSyncUpdate( + summary: UpdateSummary, + rooms: List + ) = 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() + } } \ No newline at end of file diff --git a/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/roomlist/RoomListViewState.kt b/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/roomlist/RoomListViewState.kt index 903e7d9b6f..d66a31e41f 100644 --- a/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/roomlist/RoomListViewState.kt +++ b/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/roomlist/RoomListViewState.kt @@ -3,9 +3,13 @@ package io.element.android.x.ui.screen.roomlist import com.airbnb.mvrx.Async import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized +import org.matrix.rustcomponents.sdk.Room +import org.matrix.rustcomponents.sdk.UpdateSummary data class RoomListViewState( - val list: List = emptyList(), + val user: MatrixUser = MatrixUser(), + val rooms: Async> = Uninitialized, + val summary: Async = Uninitialized, val canLoadMore: Boolean = false, val logoutAction: Async = Uninitialized, ) : MavericksState