Update the chat screen UI using RoomInfo. (#1640)

* Update the chat screen UI using `RoomInfo`.

This is specially useful for getting live values for `hasRoomCall`.

* Ensure the first `MatrixRoomInfo` is emitted ASAP

* Try excluding `*Present$present$*` inner functions from kover as separate entities

* Update strings

---------

Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
Jorge Martin Espinosa 2023-10-26 16:27:42 +02:00 committed by GitHub
parent 3ec62ad58a
commit 030e86f56b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 338 additions and 52 deletions

View file

@ -200,6 +200,7 @@ koverMerged {
"*Node$*",
// Exclude `:libraries:matrix:impl` module, it contains only wrappers to access the Rust Matrix SDK api, so it is not really relevant to unit test it: there is no logic to test.
"io.element.android.libraries.matrix.impl.*",
"*Presenter\$present\$*"
)
)
}
@ -252,6 +253,7 @@ koverMerged {
excludes += "io.element.android.appnav.loggedin.LoggedInPresenter$*"
// Some options can't be tested at the moment
excludes += "io.element.android.features.preferences.impl.developer.DeveloperSettingsPresenter$*"
excludes += "*Presenter\$present\$*"
}
bound {
minValue = 85

1
changelog.d/1158.feature Normal file
View file

@ -0,0 +1 @@
Element Call: change the 'join call' button in a chat room when there's an active call.

View file

@ -21,6 +21,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@ -74,6 +75,7 @@ import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
import io.element.android.libraries.matrix.api.room.MessageEventType
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo
@ -113,6 +115,7 @@ class MessagesPresenter @AssistedInject constructor(
@Composable
override fun present(): MessagesState {
val roomInfo by room.roomInfoFlow.collectAsState(null)
val localCoroutineScope = rememberCoroutineScope()
val composerState = composerPresenter.present()
val voiceMessageComposerState = voiceMessageComposerPresenter.present()
@ -125,14 +128,13 @@ class MessagesPresenter @AssistedInject constructor(
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
val userHasPermissionToSendMessage by room.canSendMessageAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value)
val userHasPermissionToRedact by room.canRedactAsState(updateKey = syncUpdateFlow.value)
var roomName: Async<String> by remember { mutableStateOf(Async.Uninitialized) }
var roomAvatar: Async<AvatarData> by remember { mutableStateOf(Async.Uninitialized) }
LaunchedEffect(syncUpdateFlow.value) {
withContext(dispatchers.io) {
roomName = Async.Success(room.displayName)
roomAvatar = Async.Success(room.avatarData())
}
val roomName: Async<String> by remember {
derivedStateOf { roomInfo?.name?.let { Async.Success(it) } ?: Async.Uninitialized }
}
val roomAvatar: Async<AvatarData> by remember {
derivedStateOf { roomInfo?.avatarData()?.let { Async.Success(it) } ?: Async.Uninitialized }
}
var hasDismissedInviteDialog by rememberSaveable {
mutableStateOf(false)
}
@ -207,14 +209,15 @@ class MessagesPresenter @AssistedInject constructor(
enableVoiceMessages = enableVoiceMessages,
enableInRoomCalls = enableInRoomCalls,
appName = buildMeta.applicationName,
isCallOngoing = roomInfo?.hasRoomCall ?: false,
eventSink = { handleEvents(it) }
)
}
private fun MatrixRoom.avatarData(): AvatarData {
private fun MatrixRoomInfo.avatarData(): AvatarData {
return AvatarData(
id = roomId.value,
name = displayName,
id = id,
name = name,
url = avatarUrl,
size = AvatarSize.TimelineRoom
)

View file

@ -50,6 +50,7 @@ data class MessagesState(
val enableTextFormatting: Boolean,
val enableVoiceMessages: Boolean,
val enableInRoomCalls: Boolean,
val isCallOngoing: Boolean,
val appName: String,
val eventSink: (MessagesEvents) -> Unit
)

View file

@ -63,6 +63,9 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
attachmentsState = AttachmentsState.Sending.Uploading(0.33f)
),
),
aMessagesState().copy(
isCallOngoing = true,
)
)
}
@ -102,6 +105,7 @@ fun aMessagesState() = MessagesState(
enableTextFormatting = true,
enableVoiceMessages = true,
enableInRoomCalls = true,
isCallOngoing = false,
appName = "Element",
eventSink = {}
)

View file

@ -21,17 +21,21 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.width
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
@ -94,6 +98,7 @@ import io.element.android.libraries.theme.ElementTheme
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList
import timber.log.Timber
import androidx.compose.material3.Button as Material3Button
@Composable
fun MessagesView(
@ -174,6 +179,7 @@ fun MessagesView(
inRoomCallsEnabled = state.enableInRoomCalls,
onBackPressed = onBackPressed,
onRoomDetailsClicked = onRoomDetailsClicked,
isCallOngoing = state.isCallOngoing,
onJoinCallClicked = onJoinCallClicked,
)
}
@ -375,6 +381,7 @@ private fun MessagesViewTopBar(
roomName: String?,
roomAvatar: AvatarData?,
inRoomCallsEnabled: Boolean,
isCallOngoing: Boolean,
modifier: Modifier = Modifier,
onRoomDetailsClicked: () -> Unit = {},
onJoinCallClicked: () -> Unit = {},
@ -402,15 +409,48 @@ private fun MessagesViewTopBar(
},
actions = {
if (inRoomCallsEnabled) {
IconButton(onClick = onJoinCallClicked) {
Icon(CommonDrawables.ic_compound_video_call, contentDescription = null) // TODO add proper content description once we have the state
if (isCallOngoing) {
JoinCallMenuItem(onJoinCallClicked = onJoinCallClicked)
} else {
IconButton(onClick = onJoinCallClicked) {
Icon(CommonDrawables.ic_compound_video_call, contentDescription = stringResource(CommonStrings.a11y_start_call))
}
}
}
Spacer(Modifier.width(8.dp))
},
windowInsets = WindowInsets(0.dp)
)
}
@Composable
private fun JoinCallMenuItem(
modifier: Modifier = Modifier,
onJoinCallClicked: () -> Unit,
) {
Material3Button(
onClick = onJoinCallClicked,
colors = ButtonDefaults.buttonColors(
contentColor = ElementTheme.colors.bgCanvasDefault,
containerColor = ElementTheme.colors.iconAccentTertiary
),
contentPadding = PaddingValues(horizontal = 10.dp, vertical = 0.dp),
modifier = modifier.heightIn(min = 36.dp),
) {
Icon(
modifier = Modifier.size(20.dp),
resourceId = CommonDrawables.ic_compound_video_call,
contentDescription = null
)
Spacer(Modifier.width(8.dp))
Text(
text = stringResource(CommonStrings.action_join),
style = ElementTheme.typography.fontBodyMdMedium
)
Spacer(Modifier.width(8.dp))
}
}
@Composable
private fun RoomAvatarAndNameRow(
roomName: String,

View file

@ -68,6 +68,7 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.A_SESSION_ID_2
import io.element.android.libraries.matrix.test.core.aBuildMeta
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.aRoomInfo
import io.element.android.libraries.matrix.test.room.aRoomMember
import io.element.android.libraries.mediapickers.test.FakePickerProvider
import io.element.android.libraries.mediaupload.api.MediaSender
@ -106,7 +107,8 @@ class MessagesPresenterTest {
val initialState = consumeItemsUntilTimeout().last()
assertThat(initialState.roomId).isEqualTo(A_ROOM_ID)
assertThat(initialState.roomName).isEqualTo(Async.Success(""))
assertThat(initialState.roomAvatar).isEqualTo(Async.Success(AvatarData(id = A_ROOM_ID.value, name = "", size = AvatarSize.TimelineRoom)))
assertThat(initialState.roomAvatar)
.isEqualTo(Async.Success(AvatarData(id = A_ROOM_ID.value, name = "", url = AN_AVATAR_URL, size = AvatarSize.TimelineRoom)))
assertThat(initialState.userHasPermissionToSendMessage).isTrue()
assertThat(initialState.userHasPermissionToRedact).isFalse()
assertThat(initialState.hasNetworkConnection).isTrue()
@ -603,7 +605,9 @@ class MessagesPresenterTest {
private fun TestScope.createMessagesPresenter(
coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
matrixRoom: MatrixRoom = FakeMatrixRoom(),
matrixRoom: MatrixRoom = FakeMatrixRoom().apply {
givenRoomInfo(aRoomInfo(id = roomId.value, name = ""))
},
navigator: FakeMessagesNavigator = FakeMessagesNavigator(),
clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(),
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.api.room
enum class CurrentUserMembership {
INVITED, JOINED, LEFT
}

View file

@ -32,6 +32,7 @@ import io.element.android.libraries.matrix.api.room.location.AssetType
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import java.io.Closeable
import java.io.File
@ -51,6 +52,8 @@ interface MatrixRoom : Closeable {
val activeMemberCount: Long
val joinedMemberCount: Long
val roomInfoFlow: Flow<MatrixRoomInfo>
/**
* A one-to-one is a room with exactly 2 members.
* See [the Matrix spec](https://spec.matrix.org/latest/client-server-api/#default-underride-rules).

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.api.room
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
data class MatrixRoomInfo(
val id: String,
val name: String?,
val topic: String?,
val avatarUrl: String?,
val isDirect: Boolean,
val isPublic: Boolean,
val isSpace: Boolean,
val isTombstoned: Boolean,
val canonicalAlias: String?,
val alternativeAliases: List<String>,
val currentUserMembership: CurrentUserMembership,
val latestEvent: EventTimelineItem?,
val inviter: RoomMember?,
val activeMembersCount: Long,
val invitedMembersCount: Long,
val joinedMembersCount: Long,
val highlightCount: Long,
val notificationCount: Long,
val userDefinedNotificationMode: RoomNotificationMode?,
val hasRoomCall: Boolean,
val activeRoomCallParticipants: List<String>
)

View file

@ -47,6 +47,7 @@ import io.element.android.libraries.matrix.impl.notification.RustNotificationSer
import io.element.android.libraries.matrix.impl.notificationsettings.RustNotificationSettingsService
import io.element.android.libraries.matrix.impl.oidc.toRustAction
import io.element.android.libraries.matrix.impl.pushers.RustPushersService
import io.element.android.libraries.matrix.impl.room.MatrixRoomInfoMapper
import io.element.android.libraries.matrix.impl.room.RoomContentForwarder
import io.element.android.libraries.matrix.impl.room.RoomSyncSubscriber
import io.element.android.libraries.matrix.impl.room.RustMatrixRoom
@ -201,7 +202,8 @@ class RustMatrixClient constructor(
systemClock = clock,
roomContentForwarder = roomContentForwarder,
sessionData = sessionStore.getSession(sessionId.value)!!,
roomSyncSubscriber = roomSyncSubscriber
roomSyncSubscriber = roomSyncSubscriber,
matrixRoomInfoMapper = MatrixRoomInfoMapper(),
)
}
}

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.impl.room
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper
import org.matrix.rustcomponents.sdk.use
import org.matrix.rustcomponents.sdk.Membership as RustMembership
import org.matrix.rustcomponents.sdk.RoomInfo as RustRoomInfo
import org.matrix.rustcomponents.sdk.RoomNotificationMode as RustRoomNotificationMode
class MatrixRoomInfoMapper(
private val timelineItemMapper: EventTimelineItemMapper = EventTimelineItemMapper(),
) {
fun map(rustRoomInfo: RustRoomInfo): MatrixRoomInfo = rustRoomInfo.use {
return MatrixRoomInfo(
id = it.id,
name = it.name,
topic = it.topic,
avatarUrl = it.avatarUrl,
isDirect = it.isDirect,
isPublic = it.isPublic,
isSpace = it.isSpace,
isTombstoned = it.isTombstoned,
canonicalAlias = it.canonicalAlias,
alternativeAliases = it.alternativeAliases,
currentUserMembership = it.membership.map(),
latestEvent = it.latestEvent?.use (timelineItemMapper::map),
inviter = it.inviter?.use(RoomMemberMapper::map),
activeMembersCount = it.activeMembersCount.toLong(),
invitedMembersCount = it.invitedMembersCount.toLong(),
joinedMembersCount = it.joinedMembersCount.toLong(),
highlightCount = it.highlightCount.toLong(),
notificationCount = it.notificationCount.toLong(),
userDefinedNotificationMode = it.userDefinedNotificationMode?.map(),
hasRoomCall = it.hasRoomCall,
activeRoomCallParticipants = it.activeRoomCallParticipants
)
}
}
fun RustMembership.map(): CurrentUserMembership = when(this) {
RustMembership.INVITED -> CurrentUserMembership.INVITED
RustMembership.JOINED -> CurrentUserMembership.JOINED
RustMembership.LEFT -> CurrentUserMembership.LEFT
}
fun RustRoomNotificationMode.map(): RoomNotificationMode = when(this) {
RustRoomNotificationMode.ALL_MESSAGES -> RoomNotificationMode.ALL_MESSAGES
RustRoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY -> RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
RustRoomNotificationMode.MUTE -> RoomNotificationMode.MUTE
}

View file

@ -32,6 +32,7 @@ import io.element.android.libraries.matrix.api.media.MediaUploadHandler
import io.element.android.libraries.matrix.api.media.VideoInfo
import io.element.android.libraries.matrix.api.poll.PollKind
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
import io.element.android.libraries.matrix.api.room.MatrixRoomNotificationSettingsState
import io.element.android.libraries.matrix.api.room.MessageEventType
@ -50,6 +51,7 @@ import io.element.android.libraries.matrix.impl.poll.toInner
import io.element.android.libraries.matrix.impl.room.location.toInner
import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline
import io.element.android.libraries.matrix.impl.util.destroyAll
import io.element.android.libraries.matrix.impl.util.mxCallbackFlow
import io.element.android.libraries.matrix.impl.widget.RustWidgetDriver
import io.element.android.libraries.matrix.impl.widget.generateWidgetWebViewUrl
import io.element.android.libraries.sessionstorage.api.SessionData
@ -59,12 +61,16 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.EventTimelineItem
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.RoomInfo
import org.matrix.rustcomponents.sdk.RoomInfoListener
import org.matrix.rustcomponents.sdk.RoomListItem
import org.matrix.rustcomponents.sdk.RoomMember
import org.matrix.rustcomponents.sdk.RoomMessageEventContentWithoutRelation
@ -73,6 +79,7 @@ import org.matrix.rustcomponents.sdk.WidgetCapabilities
import org.matrix.rustcomponents.sdk.WidgetCapabilitiesProvider
import org.matrix.rustcomponents.sdk.messageEventContentFromHtml
import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown
import org.matrix.rustcomponents.sdk.use
import timber.log.Timber
import java.io.File
@ -88,10 +95,23 @@ class RustMatrixRoom(
private val roomContentForwarder: RoomContentForwarder,
private val sessionData: SessionData,
private val roomSyncSubscriber: RoomSyncSubscriber,
private val matrixRoomInfoMapper: MatrixRoomInfoMapper,
) : MatrixRoom {
override val roomId = RoomId(innerRoom.id())
override val roomInfoFlow: Flow<MatrixRoomInfo> = mxCallbackFlow {
launch {
val initial = innerRoom.roomInfo().use(matrixRoomInfoMapper::map)
channel.trySend(initial)
}
innerRoom.subscribeToRoomInfoUpdates(object : RoomInfoListener {
override fun call(roomInfo: RoomInfo) {
channel.trySend(matrixRoomInfoMapper.map(roomInfo))
}
})
}
// Create a dispatcher for all room methods...
private val roomDispatcher = coroutineDispatchers.io.limitedParallelism(32)

View file

@ -68,7 +68,11 @@ class RustWidgetDriver(
}
override suspend fun send(message: String) {
driverAndHandle.handle.send(message)
try {
driverAndHandle.handle.send(message)
} catch (e: IllegalStateException) {
// The handle is closed, ignore
}
}
override fun close() {

View file

@ -29,16 +29,23 @@ import io.element.android.libraries.matrix.api.media.MediaUploadHandler
import io.element.android.libraries.matrix.api.media.VideoInfo
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
import io.element.android.libraries.matrix.api.poll.PollKind
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
import io.element.android.libraries.matrix.api.room.MatrixRoomNotificationSettingsState
import io.element.android.libraries.matrix.api.room.MessageEventType
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.api.room.StateEventType
import io.element.android.libraries.matrix.api.room.location.AssetType
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_NAME
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
@ -46,6 +53,8 @@ import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline
import io.element.android.libraries.matrix.test.widget.FakeWidgetDriver
import io.element.android.tests.testutils.simulateLongTask
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import java.io.File
@ -143,6 +152,9 @@ class FakeMatrixRoom(
private var leaveRoomError: Throwable? = null
private val _roomInfoFlow: MutableSharedFlow<MatrixRoomInfo> = MutableStateFlow(aRoomInfo())
override val roomInfoFlow: Flow<MatrixRoomInfo> = _roomInfoFlow
override val membersStateFlow: MutableStateFlow<MatrixRoomMembersState> = MutableStateFlow(MatrixRoomMembersState.Unknown)
override val roomNotificationSettingsStateFlow: MutableStateFlow<MatrixRoomNotificationSettingsState> =
@ -497,6 +509,10 @@ class FakeMatrixRoom(
fun givenGetWidgetDriverResult(result: Result<MatrixWidgetDriver>) {
getWidgetDriverResult = result
}
fun givenRoomInfo(roomInfo: MatrixRoomInfo) {
_roomInfoFlow.tryEmit(roomInfo)
}
}
data class SendLocationInvocation(
@ -523,3 +539,49 @@ data class EndPollInvocation(
val pollStartId: EventId,
val text: String,
)
fun aRoomInfo(
id: String = A_ROOM_ID.value,
name: String? = A_ROOM_NAME,
topic: String? = "A topic",
avatarUrl: String? = AN_AVATAR_URL,
isDirect: Boolean = false,
isPublic: Boolean = true,
isSpace: Boolean = false,
isTombstoned: Boolean = false,
canonicalAlias: String? = null,
alternativeAliases: List<String> = emptyList(),
currentUserMembership: CurrentUserMembership = CurrentUserMembership.JOINED,
latestEvent: EventTimelineItem? = null,
inviter: RoomMember? = null,
activeMembersCount: Long = 1,
invitedMembersCount: Long = 0,
joinedMembersCount: Long = 1,
highlightCount: Long = 0,
notificationCount: Long = 0,
userDefinedNotificationMode: RoomNotificationMode? = null,
hasRoomCall: Boolean = false,
activeRoomCallParticipants: List<String> = emptyList()
) = MatrixRoomInfo(
id = id,
name = name,
topic = topic,
avatarUrl = avatarUrl,
isDirect = isDirect,
isPublic = isPublic,
isSpace = isSpace,
isTombstoned = isTombstoned,
canonicalAlias = canonicalAlias,
alternativeAliases = alternativeAliases,
currentUserMembership = currentUserMembership,
latestEvent = latestEvent,
inviter = inviter,
activeMembersCount = activeMembersCount,
invitedMembersCount = invitedMembersCount,
joinedMembersCount = joinedMembersCount,
highlightCount = highlightCount,
notificationCount = notificationCount,
userDefinedNotificationMode = userDefinedNotificationMode,
hasRoomCall = hasRoomCall,
activeRoomCallParticipants = activeRoomCallParticipants
)

View file

@ -11,6 +11,7 @@
<string name="a11y_poll_end">"Ended poll"</string>
<string name="a11y_send_files">"Send files"</string>
<string name="a11y_show_password">"Show password"</string>
<string name="a11y_start_call">"Start a call"</string>
<string name="a11y_user_menu">"User menu"</string>
<string name="a11y_voice_message_record">"Record voice message. Double tap and hold to record. Release to end recording."</string>
<string name="action_accept">"Accept"</string>

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5f219bd9b9363f237e15bb73655dd53b2ec143e18c8544c11efbfa90390e091c
size 54312
oid sha256:0eee7139a7675a08899accf49a97139b56333737e649e51ca1644ae6ae68d915
size 54358

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6065f5330e1b3638719c6743db098bf6576fcffc6ccf8215f7f600a0b981144b
size 55731
oid sha256:39e0b212a2cc423f3e33254d4a965c3071e88e5dd21268c42b3b555b1800b236
size 55774

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:33afbe67d0e4156d615e6a58d4356386d5e2c0922cf646c2ec385338d869d8f5
size 56023

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f4bdafdfa50665f05ba5cd8749252e7e5d65bea8a8c6bfc1c46eb1acd1570b52
size 56086
oid sha256:42445e54ca2cfadbbd3f041f327825ea5595e27877376a760c37b746529d0980
size 56135

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2bfcc4dcaa8980cfc9c724e64a93116ac1fb81a1bf4421532cb196d4e07db7c5
size 56024
oid sha256:b9f1300da0ac7ef29286fb37d4489b38fa07632f71aa7d1f1cc0e0786749c626
size 56124

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c86a6da3d45b767a41f65e0cf4e8cacefb33e0409386a6233140891deb2258f6
size 51965
oid sha256:95f81914a56679419a7a6055040ae51b11616c87012310cbd221e3af41d55e8e
size 52039

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5a1fc20759ff45eeef547621853b9684a663fe9dcdf78b316454dcae26d078f9
size 52286
oid sha256:ce97ccc29b01824ddd251dbe7756aa80749c0acc4fd8278cb1c3e4fc24c2337e
size 52334

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7b65df67b1384aaa169f25d81e87072f2732c88676122e58e5d08850ada050ce
size 60073
oid sha256:6d387f9678367ec0b8d6617a3ba378d309979af0e534e9f683805e71cd6c2794
size 60172

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:abe449182a4cddbf11b9004579d27043e41c74d45261c1435d18b516946f9eed
size 42175
oid sha256:0708776dd8884bd8975845c9fbb0b30fe6317d5692aaba36781fed5cff065c1b
size 42279

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5deb0929efaab3160a7d0441bde9afe278064e8c0641cc62408bb25acbdced21
size 41329
oid sha256:766086d64f70b2a9f028149190d3f3c5b2ff34599907b38997657b6d093fa0f4
size 41428

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7b9bc4654b6911d7f50b4b0242b650a529cb22c13a58caf5eae50366a2d27b37
size 52532
oid sha256:6d42806b454f15ed83e883bf5e2765d345163a57049b4b598c1b8d08458588dc
size 52624

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0e051668beb9030ff184d6e75831a00202854f6448880f399a7ee2d5be187740
size 53880
oid sha256:27f4fd957e004579b8b20373e1649f8c067a604595a5dcaae60b0caec7c43954
size 53972

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:010d76d537104b914a35cdb783c5cfcc1953a470d2d9bc23781c8837c9558f5f
size 54122

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c6c6603452db218811f3f5a2baa934ca640755b969e81ca8cbf6b3dcf663aee9
size 54551
oid sha256:397f061ceeefb2ea67a5d9a342050cea136554e936854bd26b92c3acfde45d74
size 54646

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e953bc91e9959f1635d12416f6afde79427aeac4edef44fb38f1512672e544bd
size 51552
oid sha256:a1a0280d6321b754e5e36d25eefc2d5c7192af16e52ae1304b4e7c57a7824e19
size 51611

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0157713ae934c0771a5d3d4d064ce6dc9be11b1d8011f2360740d7f4a20f6f04
size 50162
oid sha256:99ded72c99dc1ba5b8c61cbacca6cc91f4b6325a16c12013150c6d63afd28182
size 50273

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:24f1d30bb62ed620671e87062a338fd53f409bce6e08fb7b116249decf74e409
size 50295
oid sha256:d2fc04a6c21e851b4e10c88ad9485f43f2f1576f5aeb40d4611513352341536e
size 50384

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9457cbf3ff35a5677f983eba7d5087ca82e4d99a2bb942f64f5f7ccdb71e01b5
size 54882
oid sha256:5f3a764349e13ebe7205ff4d0b04a02f64c6eee5d65bb3b9cc62ac1d721d2d39
size 54944

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:549892647be61e9fd5fbbb80fa14734533e1b4628f95d409f2772dd990000f42
size 38953
oid sha256:459029e7871910e085378a40ff503d286b72fb7029c251d1e04a8a1f39cfd0ba
size 39017

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c6bef454d8f57ba76b34b8588ba73635719feb6b1dde967b90f812d2410a7749
size 38165
oid sha256:a2d6cb8081da15f265d35e909c1d22bb6a48219162b20e1472230a698fdc2d2b
size 38231