Browse Source

First mapping of room. Still very dirty

feature/bma/flipper
Benoit Marty 2 years ago
parent
commit
501dd176e5
  1. 14
      libraries/core/src/main/java/io/element/android/x/core/data/Try.kt
  2. 2
      libraries/sdk/matrix/build.gradle
  3. 16
      libraries/sdk/matrix/src/main/java/io/element/android/x/sdk/matrix/MatrixClient.kt
  4. 15
      libraries/sdk/matrix/src/main/java/io/element/android/x/sdk/matrix/RoomWrapper.kt
  5. 35
      libraries/ui/screens/login/src/main/java/io/element/android/x/ui/screen/login/LoginActivity.kt
  6. 1
      libraries/ui/screens/roomlist/build.gradle
  7. 6
      libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/roomlist/MatrixUser.kt
  8. 3
      libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/roomlist/RoomListActions.kt
  9. 46
      libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/roomlist/RoomListActivity.kt
  10. 51
      libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/roomlist/RoomListViewModel.kt
  11. 6
      libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/roomlist/RoomListViewState.kt

14
libraries/core/src/main/java/io/element/android/x/core/data/Try.kt

@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
package io.element.android.x.core.data
import android.util.Log
inline fun <A> tryOrNull(message: String? = null, operation: () -> A): A? {
return try {
operation()
} catch (any: Throwable) {
if (message != null) {
Log.e("TAG", message, any)
}
null
}
}

2
libraries/sdk/matrix/build.gradle

@ -28,7 +28,7 @@ android { @@ -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'

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

@ -8,6 +8,8 @@ class MatrixClient internal constructor( @@ -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( @@ -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( @@ -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( @@ -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<Room>)
}
}

15
libraries/sdk/matrix/src/main/java/io/element/android/x/sdk/matrix/RoomWrapper.kt

@ -0,0 +1,15 @@ @@ -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 }
}
}

35
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 @@ -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 @@ -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() { @@ -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(

1
libraries/ui/screens/roomlist/build.gradle

@ -58,4 +58,5 @@ dependencies { @@ -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'
}

6
libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/roomlist/MatrixUser.kt

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
package io.element.android.x.ui.screen.roomlist
data class MatrixUser(
val username: String? = null,
val avatarUrl: String? = null,
)

3
libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/roomlist/RoomListActions.kt

@ -1,6 +1,7 @@ @@ -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
}

46
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 @@ -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 @@ -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() { @@ -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() { @@ -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")
}
}
)

51
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 @@ -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<RoomListViewState>(initialState) {
MavericksViewModel<RoomListViewState>(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) : @@ -35,4 +56,32 @@ class RoomListViewModel(initialState: RoomListViewState) :
private suspend fun getClient(): MatrixClient {
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()
}
}

6
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 @@ -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<String> = emptyList(),
val user: MatrixUser = MatrixUser(),
val rooms: Async<List<Room>> = Uninitialized,
val summary: Async<UpdateSummary> = Uninitialized,
val canLoadMore: Boolean = false,
val logoutAction: Async<Unit> = Uninitialized,
) : MavericksState

Loading…
Cancel
Save