First mapping of room. Still very dirty
This commit is contained in:
parent
58decf03d8
commit
f906158f2b
11 changed files with 165 additions and 30 deletions
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -28,7 +28,7 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
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 "net.java.dev.jna:jna:5.10.0@aar"
|
||||||
implementation 'androidx.datastore:datastore-core:1.0.0'
|
implementation 'androidx.datastore:datastore-core:1.0.0'
|
||||||
implementation 'androidx.datastore:datastore-preferences:1.0.0'
|
implementation 'androidx.datastore:datastore-preferences:1.0.0'
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ class MatrixClient internal constructor(
|
||||||
private val client: Client,
|
private val client: Client,
|
||||||
private val sessionStore: SessionStore,
|
private val sessionStore: SessionStore,
|
||||||
) {
|
) {
|
||||||
|
private val roomWrapper = RoomWrapper(client)
|
||||||
|
|
||||||
fun startSync() {
|
fun startSync() {
|
||||||
val clientDelegate = object : ClientDelegate {
|
val clientDelegate = object : ClientDelegate {
|
||||||
override fun didReceiveAuthError(isSoftLogout: Boolean) {
|
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()
|
val slidingSyncView = SlidingSyncViewBuilder()
|
||||||
.timelineLimit(limit = 10u)
|
.timelineLimit(limit = 10u)
|
||||||
.requiredState(requiredState = listOf(RequiredState(key = "m.room.avatar", value = "")))
|
.requiredState(requiredState = listOf(RequiredState(key = "m.room.avatar", value = "")))
|
||||||
|
|
@ -49,7 +51,10 @@ class MatrixClient internal constructor(
|
||||||
slidingSync.setObserver(object : SlidingSyncObserver {
|
slidingSync.setObserver(object : SlidingSyncObserver {
|
||||||
override fun didReceiveSyncUpdate(summary: UpdateSummary) {
|
override fun didReceiveSyncUpdate(summary: UpdateSummary) {
|
||||||
Log.v(LOG_TAG, "didReceiveSyncUpdate=$summary")
|
Log.v(LOG_TAG, "didReceiveSyncUpdate=$summary")
|
||||||
onSyncUpdate.invoke(summary)
|
val rooms = summary.rooms.mapNotNull {
|
||||||
|
roomWrapper.getRoom(it)
|
||||||
|
}
|
||||||
|
listener.onSyncUpdate(summary, rooms)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return slidingSync.sync()
|
return slidingSync.sync()
|
||||||
|
|
@ -59,4 +64,11 @@ class MatrixClient internal constructor(
|
||||||
client.logout()
|
client.logout()
|
||||||
sessionStore.reset()
|
sessionStore.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun username(): String = client.displayName()
|
||||||
|
fun avatarUrl(): String = client.avatarUrl()
|
||||||
|
|
||||||
|
interface SlidingSyncListener {
|
||||||
|
fun onSyncUpdate(summary: UpdateSummary, rooms: List<Room>)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,13 +7,13 @@ import androidx.activity.compose.setContent
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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 androidx.compose.ui.unit.dp
|
||||||
import com.airbnb.mvrx.Fail
|
import com.airbnb.mvrx.Fail
|
||||||
import com.airbnb.mvrx.Loading
|
import com.airbnb.mvrx.Loading
|
||||||
|
|
@ -22,10 +22,10 @@ import com.airbnb.mvrx.compose.collectAsState
|
||||||
import com.airbnb.mvrx.compose.mavericksViewModel
|
import com.airbnb.mvrx.compose.mavericksViewModel
|
||||||
import io.element.android.x.ui.theme.ElementXTheme
|
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.VectorButton
|
||||||
import io.element.android.x.ui.theme.components.VectorTextField
|
|
||||||
|
|
||||||
class LoginActivity : ComponentActivity() {
|
class LoginActivity : ComponentActivity() {
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
|
@ -44,21 +44,34 @@ class LoginActivity : ComponentActivity() {
|
||||||
val viewModel: LoginViewModel = mavericksViewModel()
|
val viewModel: LoginViewModel = mavericksViewModel()
|
||||||
val state by viewModel.collectAsState()
|
val state by viewModel.collectAsState()
|
||||||
val isError = state.isLoggedIn is Fail
|
val isError = state.isLoggedIn is Fail
|
||||||
VectorTextField(value = state.homeserver,
|
OutlinedTextField(
|
||||||
|
value = state.homeserver,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
viewModel.handle(LoginActions.SetHomeserver(it))
|
viewModel.handle(LoginActions.SetHomeserver(it))
|
||||||
})
|
},
|
||||||
VectorTextField(
|
keyboardOptions = KeyboardOptions(
|
||||||
|
keyboardType = KeyboardType.Uri,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
OutlinedTextField(
|
||||||
value = state.login,
|
value = state.login,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
viewModel.handle(LoginActions.SetLogin(it))
|
viewModel.handle(LoginActions.SetLogin(it))
|
||||||
})
|
},
|
||||||
VectorTextField(
|
keyboardOptions = KeyboardOptions(
|
||||||
|
keyboardType = KeyboardType.Text,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
OutlinedTextField(
|
||||||
value = state.password,
|
value = state.password,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
viewModel.handle(LoginActions.SetPassword(it))
|
viewModel.handle(LoginActions.SetPassword(it))
|
||||||
},
|
},
|
||||||
isError = isError
|
isError = isError,
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
keyboardType = KeyboardType.Password,
|
||||||
|
imeAction = ImeAction.Send,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
if (isError) {
|
if (isError) {
|
||||||
Text(
|
Text(
|
||||||
|
|
|
||||||
|
|
@ -58,4 +58,5 @@ dependencies {
|
||||||
implementation 'androidx.fragment:fragment-ktx:1.5.3'
|
implementation 'androidx.fragment:fragment-ktx:1.5.3'
|
||||||
|
|
||||||
implementation 'com.airbnb.android:mavericks-compose:2.7.0'
|
implementation 'com.airbnb.android:mavericks-compose:2.7.0'
|
||||||
|
implementation 'io.coil-kt:coil-compose:2.2.1'
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package io.element.android.x.ui.screen.roomlist
|
||||||
|
|
||||||
|
data class MatrixUser(
|
||||||
|
val username: String? = null,
|
||||||
|
val avatarUrl: String? = null,
|
||||||
|
)
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package io.element.android.x.ui.screen.roomlist
|
package io.element.android.x.ui.screen.roomlist
|
||||||
|
|
||||||
sealed interface RoomListActions {
|
sealed interface RoomListActions {
|
||||||
object Logout : RoomListActions
|
object Init : RoomListActions
|
||||||
object LoadMore : RoomListActions
|
object LoadMore : RoomListActions
|
||||||
|
object Logout : RoomListActions
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,15 @@ package io.element.android.x.ui.screen.roomlist
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ExitToApp
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import coil.compose.rememberAsyncImagePainter
|
||||||
import com.airbnb.mvrx.Success
|
import com.airbnb.mvrx.Success
|
||||||
import com.airbnb.mvrx.compose.collectAsState
|
import com.airbnb.mvrx.compose.collectAsState
|
||||||
import com.airbnb.mvrx.compose.mavericksViewModel
|
import com.airbnb.mvrx.compose.mavericksViewModel
|
||||||
|
|
@ -17,12 +19,18 @@ import io.element.android.x.ui.theme.ElementXTheme
|
||||||
|
|
||||||
class RoomListActivity : ComponentActivity() {
|
class RoomListActivity : ComponentActivity() {
|
||||||
|
|
||||||
|
private var initDone = false
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
ElementXTheme {
|
ElementXTheme {
|
||||||
val viewModel: RoomListViewModel = mavericksViewModel()
|
val viewModel: RoomListViewModel = mavericksViewModel()
|
||||||
|
if (!initDone) {
|
||||||
|
initDone = true
|
||||||
|
viewModel.handle(RoomListActions.Init)
|
||||||
|
}
|
||||||
val state = viewModel.collectAsState()
|
val state = viewModel.collectAsState()
|
||||||
// A surface container using the 'background' color from the theme
|
// A surface container using the 'background' color from the theme
|
||||||
Surface(
|
Surface(
|
||||||
|
|
@ -34,12 +42,13 @@ class RoomListActivity : ComponentActivity() {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
OptionMenu(viewModel)
|
OptionMenu(state.value.user, viewModel)
|
||||||
/* TODO
|
val rooms = state.value.rooms
|
||||||
val state = viewModel.state.collectAsState().value
|
if (rooms is Success) {
|
||||||
RoomListHeader()
|
rooms().forEach {
|
||||||
RoomList()
|
Text(text = "Room: ${it.name() ?: it.id()}")
|
||||||
*/
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (state.value.logoutAction is Success) {
|
if (state.value.logoutAction is Success) {
|
||||||
|
|
@ -51,14 +60,25 @@ class RoomListActivity : ComponentActivity() {
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun OptionMenu(viewModel: RoomListViewModel) {
|
private fun OptionMenu(matrixUser: MatrixUser, viewModel: RoomListViewModel) {
|
||||||
TopAppBar(
|
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 = {
|
actions = {
|
||||||
Button(
|
IconButton(
|
||||||
onClick = { viewModel.handle(RoomListActions.Logout) }
|
onClick = { viewModel.handle(RoomListActions.Logout) }
|
||||||
) {
|
) {
|
||||||
Text(text = "logout")
|
Icon(Icons.Default.ExitToApp, contentDescription = "logout")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -4,22 +4,43 @@ import com.airbnb.mvrx.Fail
|
||||||
import com.airbnb.mvrx.Loading
|
import com.airbnb.mvrx.Loading
|
||||||
import com.airbnb.mvrx.MavericksViewModel
|
import com.airbnb.mvrx.MavericksViewModel
|
||||||
import com.airbnb.mvrx.Success
|
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.MatrixClient
|
||||||
import io.element.android.x.sdk.matrix.MatrixInstance
|
import io.element.android.x.sdk.matrix.MatrixInstance
|
||||||
import kotlinx.coroutines.launch
|
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) :
|
class RoomListViewModel(initialState: RoomListViewState) :
|
||||||
MavericksViewModel<RoomListViewState>(initialState) {
|
MavericksViewModel<RoomListViewState>(initialState), MatrixClient.SlidingSyncListener {
|
||||||
|
|
||||||
|
private var sync: StoppableSpawn? = null
|
||||||
private val matrix = MatrixInstance.getInstance()
|
private val matrix = MatrixInstance.getInstance()
|
||||||
|
|
||||||
fun handle(action: RoomListActions) {
|
fun handle(action: RoomListActions) {
|
||||||
when (action) {
|
when (action) {
|
||||||
|
RoomListActions.Init -> handleInit()
|
||||||
RoomListActions.LoadMore -> TODO()
|
RoomListActions.LoadMore -> TODO()
|
||||||
RoomListActions.Logout -> handleLogout()
|
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() {
|
private fun handleLogout() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
setState { copy(logoutAction = Loading()) }
|
setState { copy(logoutAction = Loading()) }
|
||||||
|
|
@ -35,4 +56,32 @@ class RoomListViewModel(initialState: RoomListViewState) :
|
||||||
private suspend fun getClient(): MatrixClient {
|
private suspend fun getClient(): MatrixClient {
|
||||||
return matrix.restoreSession()!!
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,9 +3,13 @@ package io.element.android.x.ui.screen.roomlist
|
||||||
import com.airbnb.mvrx.Async
|
import com.airbnb.mvrx.Async
|
||||||
import com.airbnb.mvrx.MavericksState
|
import com.airbnb.mvrx.MavericksState
|
||||||
import com.airbnb.mvrx.Uninitialized
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
import org.matrix.rustcomponents.sdk.Room
|
||||||
|
import org.matrix.rustcomponents.sdk.UpdateSummary
|
||||||
|
|
||||||
data class RoomListViewState(
|
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 canLoadMore: Boolean = false,
|
||||||
val logoutAction: Async<Unit> = Uninitialized,
|
val logoutAction: Async<Unit> = Uninitialized,
|
||||||
) : MavericksState
|
) : MavericksState
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue