[Room member list] Display room member list (#276)

* Implement room member list

* Move timeline initialization back to `TimelinePresenter`.

* Fix crash when the `innerRoom` inside a `RustMatrixRoom` is destroyed but `syncUpdateFlow` is still running.

* Address review comments
This commit is contained in:
Jorge Martin Espinosa
2023-04-04 18:07:57 +02:00
committed by GitHub
parent f0b95d30be
commit d7a6779343
62 changed files with 1159 additions and 157 deletions

View File

@@ -57,3 +57,7 @@ suspend fun <T> (suspend () -> Result<T>).executeResult(state: MutableState<Asyn
}
)
}
fun <T> Async<T>.isLoading(): Boolean {
return this is Async.Loading<T>
}

View File

@@ -25,7 +25,9 @@ import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.progressSemantics
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.BugReport
import androidx.compose.material3.MaterialTheme
@@ -39,6 +41,7 @@ import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.components.preferences.components.PreferenceIcon
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.Text
@Composable
@@ -47,6 +50,7 @@ fun PreferenceText(
modifier: Modifier = Modifier,
subtitle: String? = null,
currentValue: String? = null,
loadingCurrentValue: Boolean = false,
icon: ImageVector? = null,
tintColor: Color? = null,
onClick: () -> Unit = {},
@@ -56,11 +60,13 @@ fun PreferenceText(
modifier = modifier
.fillMaxWidth()
.defaultMinSize(minHeight = minHeight)
.padding(end = preferencePaddingHorizontal)
.clickable { onClick() },
.clickable { onClick() }
.padding(end = preferencePaddingHorizontal),
) {
Row(
modifier = Modifier.fillMaxWidth().padding(vertical = preferencePaddingVertical)
modifier = Modifier
.fillMaxWidth()
.padding(vertical = preferencePaddingVertical)
) {
PreferenceIcon(icon = icon, tintColor = tintColor)
Column(modifier = Modifier
@@ -88,7 +94,11 @@ fun PreferenceText(
if (currentValue != null) {
Text(currentValue, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.secondary)
Spacer(Modifier.width(16.dp))
} else if (loadingCurrentValue) {
CircularProgressIndicator(modifier = Modifier.progressSemantics().size(20.dp), strokeWidth = 2.dp)
Spacer(Modifier.width(16.dp))
}
}
}
}

View File

@@ -31,9 +31,11 @@ interface MatrixRoom: Closeable {
val alternativeAliases: List<String>
val topic: String?
val avatarUrl: String?
val members: List<RoomMember>
val isEncrypted: Boolean
suspend fun members() : List<RoomMember>
suspend fun memberCount(): Int
fun syncUpdateFlow(): Flow<Long>
fun timeline(): MatrixTimeline

View File

@@ -17,6 +17,7 @@
package io.element.android.libraries.matrix.impl.room
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.MatrixRoom
@@ -24,10 +25,12 @@ import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.SlidingSyncRoom
@@ -43,10 +46,32 @@ class RustMatrixRoom(
private val coroutineDispatchers: CoroutineDispatchers,
) : MatrixRoom {
private var loadMembersJob: Job? = null
private var cachedMembers: List<RoomMember> = emptyList()
override suspend fun members(): List<RoomMember> {
return cachedMembers.ifEmpty {
if (loadMembersJob == null) {
loadMembersJob = coroutineScope.launch(coroutineDispatchers.io) {
cachedMembers = tryOrNull {
innerRoom.members().map(RoomMemberMapper::map)
} ?: emptyList()
}
}
loadMembersJob?.join()
loadMembersJob = null
cachedMembers
}
}
override suspend fun memberCount(): Int {
return members().size
}
override fun syncUpdateFlow(): Flow<Long> {
return slidingSyncUpdateFlow
.filter {
it.rooms.contains(innerRoom.id())
it.rooms.contains(roomId.value)
}
.map {
System.currentTimeMillis()
@@ -95,9 +120,6 @@ class RustMatrixRoom(
return innerRoom.avatarUrl()
}
override val members: List<RoomMember>
get() = innerRoom.members().map(RoomMemberMapper::map)
override val isEncrypted: Boolean
get() = innerRoom.isEncrypted()

View File

@@ -87,14 +87,6 @@ class RustMatrixTimeline(
override fun initialize() {
Timber.v("Init timeline for room ${matrixRoom.roomId}")
coroutineScope.launch {
matrixRoom.fetchMembers()
.onFailure {
Timber.e(it, "Fail to fetch members for room ${matrixRoom.roomId}")
}.onSuccess {
Timber.v("Success fetching members for room ${matrixRoom.roomId}")
}
}
coroutineScope.launch {
val result = addListener(innerTimelineListener)
result

View File

@@ -34,13 +34,18 @@ class FakeMatrixRoom(
override val displayName: String = "",
override val topic: String? = null,
override val avatarUrl: String? = null,
override val members: List<RoomMember> = emptyList(),
override val isEncrypted: Boolean = false,
override val alias: String? = null,
override val alternativeAliases: List<String> = emptyList(),
private val members: List<RoomMember> = emptyList(),
private val matrixTimeline: MatrixTimeline = FakeMatrixTimeline(),
) : MatrixRoom {
private var fetchMemberResult: Result<Unit> = Result.success(Unit)
var areMembersFetched: Boolean = false
private set
override fun syncUpdateFlow(): Flow<Long> {
return emptyFlow()
}
@@ -50,7 +55,11 @@ class FakeMatrixRoom(
}
override suspend fun fetchMembers(): Result<Unit> {
return Result.success(Unit)
return fetchMemberResult.also { result ->
if (result.isSuccess) {
areMembersFetched = true
}
}
}
override suspend fun userDisplayName(userId: String): Result<String?> {
@@ -61,6 +70,18 @@ class FakeMatrixRoom(
TODO("Not yet implemented")
}
override suspend fun members(): List<RoomMember> {
return members
}
override suspend fun memberCount(): Int {
if (fetchMemberResult.isSuccess) {
return members.count()
} else {
throw fetchMemberResult.exceptionOrNull()!!
}
}
override suspend fun sendMessage(message: String): Result<Unit> {
delay(100)
return Result.success(Unit)
@@ -94,4 +115,8 @@ class FakeMatrixRoom(
}
override fun close() = Unit
fun givenFetchMemberResult(result: Result<Unit>) {
fetchMemberResult = result
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="action_confirm">"Confirmare"</string>
<string name="action_create_a_room">"Creați o cameră"</string>
<string name="action_done">"Gata"</string>
<string name="action_ok">"OK"</string>
<string name="action_report_content">"Raportează conținutul"</string>
<string name="action_start_chat">"Începe discuția"</string>
<string name="action_view_source">"Vezi sursa"</string>
</resources>

View File

@@ -48,6 +48,7 @@
<string name="common_about">"About"</string>
<string name="common_audio">"Audio"</string>
<string name="common_bubbles">"Bubbles"</string>
<string name="common_creating_room">"Creating room…"</string>
<string name="common_decryption_error">"Decryption error"</string>
<string name="common_developer_options">"Developer options"</string>
<string name="common_edited_suffix">"(edited)"</string>
@@ -122,11 +123,19 @@
</plurals>
<string name="preference_rageshake">"Rageshake to report bug"</string>
<string name="rageshake_dialog_content">"You seem to be shaking the phone in frustration. Would you like to open the bug report screen?"</string>
<string name="report_content_explanation">"Reporting this message will send its unique event ID to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images."</string>
<string name="report_content_explanation">"This message will be reported to your homeservers administrator. They will not be able to read any encrypted messages."</string>
<string name="report_content_hint">"Reason for reporting this content"</string>
<string name="room_timeline_beginning_of_room">"This is the beginning of %1$s."</string>
<string name="room_timeline_beginning_of_room_no_name">"This is the beginning of this conversation."</string>
<string name="room_timeline_read_marker_title">"New"</string>
<string name="screen_dm_details_block_alert_action">"Block"</string>
<string name="screen_dm_details_block_alert_description">"Blocked users will not be able to send you messages and all message by them will be hidden. You can reverse this action anytime."</string>
<string name="screen_dm_details_block_user">"Block user"</string>
<string name="screen_dm_details_unblock_alert_action">"Unblock"</string>
<string name="screen_dm_details_unblock_alert_description">"On unblocking the user, you will be able to see all messages by them again."</string>
<string name="screen_dm_details_unblock_user">"Unblock user"</string>
<string name="screen_report_content_block_user">"Block user"</string>
<string name="screen_report_content_block_user_hint">"Check if you want to hide all current and future messages from this user"</string>
<string name="screen_room_member_details_block_alert_action">"Block"</string>
<string name="screen_room_member_details_block_alert_description">"Blocked users will not be able to send you messages and all message by them will be hidden. You can reverse this action anytime."</string>
<string name="screen_room_member_details_block_user">"Block user"</string>