[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:
committed by
GitHub
parent
f0b95d30be
commit
d7a6779343
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
10
libraries/ui-strings/src/main/res/values-ro/translations.xml
Normal file
10
libraries/ui-strings/src/main/res/values-ro/translations.xml
Normal 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>
|
||||
@@ -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 it’s 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 homeserver’s 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>
|
||||
|
||||
Reference in New Issue
Block a user