diff --git a/changelog.d/242.feature b/changelog.d/242.feature
new file mode 100644
index 0000000000..fd82c425d3
--- /dev/null
+++ b/changelog.d/242.feature
@@ -0,0 +1 @@
+[Create and join rooms] Update room properties from room details
diff --git a/features/createroom/impl/build.gradle.kts b/features/createroom/impl/build.gradle.kts
index 65c43e8fc3..f203501315 100644
--- a/features/createroom/impl/build.gradle.kts
+++ b/features/createroom/impl/build.gradle.kts
@@ -48,8 +48,8 @@ dependencies {
implementation(projects.libraries.androidutils)
implementation(projects.libraries.mediapickers.api)
implementation(projects.libraries.mediaupload.api)
- implementation(libs.coil.compose)
implementation(projects.libraries.usersearch.impl)
+ implementation(libs.coil.compose)
api(projects.features.createroom.api)
testImplementation(libs.test.junit)
diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt
index 9082849954..a020b387cb 100644
--- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt
+++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt
@@ -17,8 +17,8 @@
package io.element.android.features.createroom.impl.configureroom
import io.element.android.features.createroom.impl.CreateRoomConfig
-import io.element.android.features.createroom.impl.configureroom.avatar.AvatarAction
import io.element.android.libraries.matrix.api.user.MatrixUser
+import io.element.android.libraries.matrix.ui.media.AvatarAction
sealed interface ConfigureRoomEvents {
data class RoomNameChanged(val name: String) : ConfigureRoomEvents
diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt
index df0013972d..ca714b1e59 100644
--- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt
+++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt
@@ -27,7 +27,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import io.element.android.features.createroom.impl.CreateRoomConfig
import io.element.android.features.createroom.impl.CreateRoomDataStore
-import io.element.android.features.createroom.impl.configureroom.avatar.AvatarAction
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.execute
@@ -37,6 +36,7 @@ import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
import io.element.android.libraries.matrix.api.createroom.RoomPreset
import io.element.android.libraries.matrix.api.createroom.RoomVisibility
+import io.element.android.libraries.matrix.ui.media.AvatarAction
import io.element.android.libraries.mediapickers.api.PickerProvider
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
import kotlinx.collections.immutable.toImmutableList
diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomState.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomState.kt
index b99d70bb13..2e34f3bda2 100644
--- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomState.kt
+++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomState.kt
@@ -16,8 +16,8 @@
package io.element.android.features.createroom.impl.configureroom
+import io.element.android.libraries.matrix.ui.media.AvatarAction
import io.element.android.features.createroom.impl.CreateRoomConfig
-import io.element.android.features.createroom.impl.configureroom.avatar.AvatarAction
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.matrix.api.core.RoomId
import kotlinx.collections.immutable.ImmutableList
diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt
index 53d46b03c9..4aae1936a9 100644
--- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt
+++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt
@@ -17,6 +17,7 @@
package io.element.android.features.createroom.impl.configureroom
import android.net.Uri
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -24,9 +25,11 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.consumeWindowInsets
+import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.selectableGroup
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ModalBottomSheetValue
import androidx.compose.material.rememberModalBottomSheetState
@@ -46,11 +49,9 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import io.element.android.features.createroom.impl.R
-import io.element.android.features.createroom.impl.components.Avatar
-import io.element.android.features.createroom.impl.components.LabelledTextField
import io.element.android.features.createroom.impl.components.RoomPrivacyOption
-import io.element.android.features.createroom.impl.configureroom.avatar.AvatarActionListView
import io.element.android.libraries.architecture.Async
+import io.element.android.libraries.designsystem.components.LabelledTextField
import io.element.android.libraries.designsystem.components.ProgressDialog
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.dialogs.RetryDialog
@@ -61,7 +62,9 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.matrix.api.core.RoomId
+import io.element.android.libraries.matrix.ui.components.AvatarActionBottomSheet
import io.element.android.libraries.matrix.ui.components.SelectedUsersList
+import io.element.android.libraries.matrix.ui.components.UnsavedAvatar
import kotlinx.coroutines.launch
import io.element.android.libraries.ui.strings.R as StringR
@@ -105,54 +108,48 @@ fun ConfigureRoomView(
)
}
) { padding ->
- LazyColumn(
+ Column(
modifier = Modifier
.padding(padding)
+ .imePadding()
+ .verticalScroll(rememberScrollState())
.consumeWindowInsets(padding),
verticalArrangement = Arrangement.spacedBy(24.dp),
) {
- item {
- RoomNameWithAvatar(
- modifier = Modifier.padding(horizontal = 16.dp),
- avatarUri = state.config.avatarUri,
- roomName = state.config.roomName.orEmpty(),
- onAvatarClick = ::onAvatarClicked,
- onRoomNameChanged = { state.eventSink(ConfigureRoomEvents.RoomNameChanged(it)) },
- )
- }
- item {
- RoomTopic(
- modifier = Modifier.padding(horizontal = 16.dp),
- topic = state.config.topic.orEmpty(),
- onTopicChanged = { state.eventSink(ConfigureRoomEvents.TopicChanged(it)) },
- )
- }
+ RoomNameWithAvatar(
+ modifier = Modifier.padding(horizontal = 16.dp),
+ avatarUri = state.config.avatarUri,
+ roomName = state.config.roomName.orEmpty(),
+ onAvatarClick = ::onAvatarClicked,
+ onRoomNameChanged = { state.eventSink(ConfigureRoomEvents.RoomNameChanged(it)) },
+ )
+ RoomTopic(
+ modifier = Modifier.padding(horizontal = 16.dp),
+ topic = state.config.topic.orEmpty(),
+ onTopicChanged = { state.eventSink(ConfigureRoomEvents.TopicChanged(it)) },
+ )
if (state.config.invites.isNotEmpty()) {
- item {
- SelectedUsersList(
- contentPadding = PaddingValues(horizontal = 24.dp),
- selectedUsers = state.config.invites,
- onUserRemoved = {
- focusManager.clearFocus()
- state.eventSink(ConfigureRoomEvents.RemoveFromSelection(it))
- },
- )
- }
- }
- item {
- RoomPrivacyOptions(
- modifier = Modifier.padding(bottom = 40.dp),
- selected = state.config.privacy,
- onOptionSelected = {
+ SelectedUsersList(
+ contentPadding = PaddingValues(horizontal = 24.dp),
+ selectedUsers = state.config.invites,
+ onUserRemoved = {
focusManager.clearFocus()
- state.eventSink(ConfigureRoomEvents.RoomPrivacyChanged(it.privacy))
+ state.eventSink(ConfigureRoomEvents.RemoveFromSelection(it))
},
)
}
+ RoomPrivacyOptions(
+ modifier = Modifier.padding(bottom = 40.dp),
+ selected = state.config.privacy,
+ onOptionSelected = {
+ focusManager.clearFocus()
+ state.eventSink(ConfigureRoomEvents.RoomPrivacyChanged(it.privacy))
+ },
+ )
}
}
- AvatarActionListView(
+ AvatarActionBottomSheet(
actions = state.avatarActions,
modalBottomSheetState = itemActionsBottomSheetState,
onActionSelected = { state.eventSink(ConfigureRoomEvents.HandleAvatarAction(it)) }
@@ -221,16 +218,17 @@ fun RoomNameWithAvatar(
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
- Avatar(
+ UnsavedAvatar(
avatarUri = avatarUri,
- onClick = onAvatarClick,
+ modifier = Modifier.clickable(onClick = onAvatarClick),
)
LabelledTextField(
label = stringResource(R.string.screen_create_room_room_name_label),
value = roomName,
placeholder = stringResource(R.string.screen_create_room_room_name_placeholder),
- onValueChange = onRoomNameChanged
+ singleLine = true,
+ onValueChange = onRoomNameChanged,
)
}
}
@@ -269,6 +267,13 @@ fun RoomPrivacyOptions(
}
}
+private fun Modifier.clearFocusOnTap(focusManager: FocusManager): Modifier =
+ pointerInput(Unit) {
+ detectTapGestures(onTap = {
+ focusManager.clearFocus()
+ })
+ }
+
@Preview
@Composable
fun ConfigureRoomViewLightPreview(@PreviewParameter(ConfigureRoomStateProvider::class) state: ConfigureRoomState) =
@@ -286,10 +291,3 @@ private fun ContentToPreview(state: ConfigureRoomState) {
)
}
-private fun Modifier.clearFocusOnTap(focusManager: FocusManager): Modifier =
- pointerInput(Unit) {
- detectTapGestures(onTap = {
- focusManager.clearFocus()
- })
- }
-
diff --git a/features/createroom/impl/src/main/res/values/localazy.xml b/features/createroom/impl/src/main/res/values/localazy.xml
index 128855932a..b3c0a6e618 100644
--- a/features/createroom/impl/src/main/res/values/localazy.xml
+++ b/features/createroom/impl/src/main/res/values/localazy.xml
@@ -1,8 +1,8 @@
"New room"
- "Invite people"
- "Add people"
+ "Invite friends to Element"
+ "Invite people"
"An error occurred when creating the room"
"Messages in this room are encrypted. Encryption can’t be disabled afterwards."
"Private room (invite only)"
diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt
index ed44cc1983..7f36b9e2b9 100644
--- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt
+++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt
@@ -21,9 +21,9 @@ import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
+import io.element.android.libraries.matrix.ui.media.AvatarAction
import io.element.android.features.createroom.impl.CreateRoomConfig
import io.element.android.features.createroom.impl.CreateRoomDataStore
-import io.element.android.features.createroom.impl.configureroom.avatar.AvatarAction
import io.element.android.features.createroom.impl.userlist.UserListDataStore
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.matrix.api.core.RoomId
diff --git a/features/invitelist/impl/src/main/res/values/localazy.xml b/features/invitelist/impl/src/main/res/values/localazy.xml
index 44f6d91267..56163ec3a8 100644
--- a/features/invitelist/impl/src/main/res/values/localazy.xml
+++ b/features/invitelist/impl/src/main/res/values/localazy.xml
@@ -6,4 +6,4 @@
"Decline chat"
"No Invites"
"%1$s invited you"
-
\ No newline at end of file
+
diff --git a/features/onboarding/impl/src/main/res/values/localazy.xml b/features/onboarding/impl/src/main/res/values/localazy.xml
index 3baebbaa6b..54d86ba247 100644
--- a/features/onboarding/impl/src/main/res/values/localazy.xml
+++ b/features/onboarding/impl/src/main/res/values/localazy.xml
@@ -1,5 +1,9 @@
+ "Sign in manually"
+ "Sign in with QR code"
+ "Create account"
+ "Communicate and collaborate securely"
"Welcome to the %1$s Beta. Supercharged, for speed and simplicity."
"Be in your Element"
\ No newline at end of file
diff --git a/features/roomdetails/impl/build.gradle.kts b/features/roomdetails/impl/build.gradle.kts
index bcd618eccd..2ad93a95f4 100644
--- a/features/roomdetails/impl/build.gradle.kts
+++ b/features/roomdetails/impl/build.gradle.kts
@@ -41,6 +41,8 @@ dependencies {
implementation(projects.libraries.elementresources)
implementation(projects.libraries.uiStrings)
implementation(projects.libraries.androidutils)
+ implementation(projects.libraries.mediapickers.api)
+ implementation(projects.libraries.mediaupload.api)
api(projects.features.roomdetails.api)
api(projects.libraries.usersearch.api)
api(projects.services.apperror.api)
@@ -52,7 +54,10 @@ dependencies {
testImplementation(libs.molecule.runtime)
testImplementation(libs.test.truth)
testImplementation(libs.test.turbine)
+ testImplementation(libs.test.mockk)
testImplementation(projects.libraries.matrix.test)
+ testImplementation(projects.libraries.mediaupload.test)
+ testImplementation(projects.libraries.mediapickers.test)
testImplementation(projects.libraries.usersearch.test)
testImplementation(projects.tests.testutils)
testImplementation(projects.features.leaveroom.fake)
diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsAction.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsAction.kt
new file mode 100644
index 0000000000..61b3da21f9
--- /dev/null
+++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsAction.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.features.roomdetails.impl
+
+sealed interface RoomDetailsAction {
+ object Edit : RoomDetailsAction
+
+ object AddTopic : RoomDetailsAction
+}
diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt
index 02783775bf..7298e0eda6 100644
--- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt
+++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt
@@ -29,6 +29,7 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint
+import io.element.android.features.roomdetails.impl.edit.RoomDetailsEditNode
import io.element.android.features.roomdetails.impl.invite.RoomInviteMembersNode
import io.element.android.features.roomdetails.impl.members.RoomMemberListNode
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsNode
@@ -59,6 +60,9 @@ class RoomDetailsFlowNode @AssistedInject constructor(
@Parcelize
object RoomMemberList : NavTarget
+ @Parcelize
+ object RoomDetailsEdit : NavTarget
+
@Parcelize
object InviteMembers : NavTarget
@@ -74,12 +78,17 @@ class RoomDetailsFlowNode @AssistedInject constructor(
backstack.push(NavTarget.RoomMemberList)
}
+ override fun editRoomDetails() {
+ backstack.push(NavTarget.RoomDetailsEdit)
+ }
+
override fun openInviteMembers() {
backstack.push(NavTarget.InviteMembers)
}
}
createNode(buildContext, listOf(roomDetailsCallback))
}
+
NavTarget.RoomMemberList -> {
val roomMemberListCallback = object : RoomMemberListNode.Callback {
override fun openRoomMemberDetails(roomMemberId: UserId) {
@@ -92,9 +101,15 @@ class RoomDetailsFlowNode @AssistedInject constructor(
}
createNode(buildContext, listOf(roomMemberListCallback))
}
+
+ NavTarget.RoomDetailsEdit -> {
+ createNode(buildContext)
+ }
+
NavTarget.InviteMembers -> {
createNode(buildContext)
}
+
is NavTarget.RoomMemberDetails -> {
val plugins = listOf(RoomMemberDetailsNode.RoomMemberDetailsInput(navTarget.roomMemberId))
createNode(buildContext, plugins)
diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt
index 8fe4f774d7..77153ad62d 100644
--- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt
+++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt
@@ -46,6 +46,7 @@ class RoomDetailsNode @AssistedInject constructor(
interface Callback : Plugin {
fun openRoomMemberList()
fun openInviteMembers()
+ fun editRoomDetails()
}
private val callbacks = plugins()
@@ -90,6 +91,10 @@ class RoomDetailsNode @AssistedInject constructor(
}
}
+ private fun onEditRoomDetails() {
+ callbacks.forEach { it.editRoomDetails() }
+ }
+
@Composable
override fun View(modifier: Modifier) {
val context = LocalContext.current
@@ -103,10 +108,18 @@ class RoomDetailsNode @AssistedInject constructor(
this.onShareMember(context, roomMember)
}
+ fun onActionClicked(action: RoomDetailsAction) {
+ when (action) {
+ RoomDetailsAction.Edit -> onEditRoomDetails()
+ RoomDetailsAction.AddTopic -> onEditRoomDetails()
+ }
+ }
+
RoomDetailsView(
state = state,
modifier = modifier,
goBack = this::navigateUp,
+ onActionClicked = ::onActionClicked,
onShareRoom = ::onShareRoom,
onShareMember = ::onShareMember,
openRoomMemberList = ::openRoomMemberList,
diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt
index 4e7d88b18e..f016b38cb3 100644
--- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt
+++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt
@@ -33,6 +33,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomMembershipState
+import io.element.android.libraries.matrix.api.room.StateEventType
import io.element.android.libraries.matrix.ui.room.getDirectRoomMember
import javax.inject.Inject
@@ -52,10 +53,23 @@ class RoomDetailsPresenter @Inject constructor(
val membersState by room.membersStateFlow.collectAsState()
val memberCount by getMemberCount(membersState)
val canInvite by getCanInvite(membersState)
+ val canEditName by getCanSendStateEvent(membersState, StateEventType.ROOM_NAME)
+ val canEditAvatar by getCanSendStateEvent(membersState, StateEventType.ROOM_AVATAR)
+ val canEditTopic by getCanSendStateEvent(membersState, StateEventType.ROOM_TOPIC)
val dmMember by room.getDirectRoomMember(membersState)
val roomMemberDetailsPresenter = roomMemberDetailsPresenter(dmMember)
val roomType = getRoomType(dmMember)
+ val topicState = remember(canEditTopic, room.topic) {
+ val topic = room.topic
+
+ when {
+ !topic.isNullOrBlank() -> RoomTopicState.ExistingTopic(topic)
+ canEditTopic -> RoomTopicState.CanAddTopic
+ else -> RoomTopicState.Hidden
+ }
+ }
+
fun handleEvents(event: RoomDetailsEvent) {
when (event) {
is RoomDetailsEvent.LeaveRoom ->
@@ -70,10 +84,11 @@ class RoomDetailsPresenter @Inject constructor(
roomName = room.name ?: room.displayName,
roomAlias = room.alias,
roomAvatarUrl = room.avatarUrl,
- roomTopic = room.topic,
+ roomTopic = topicState,
memberCount = memberCount,
isEncrypted = room.isEncrypted,
canInvite = canInvite,
+ canEdit = canEditAvatar || canEditName || canEditTopic,
roomType = roomType.value,
roomMemberDetailsState = roomMemberDetailsState,
leaveRoomState = leaveRoomState,
@@ -108,6 +123,15 @@ class RoomDetailsPresenter @Inject constructor(
return canInvite
}
+ @Composable
+ private fun getCanSendStateEvent(membersState: MatrixRoomMembersState, type: StateEventType): State {
+ val canSendEvent = remember(membersState) { mutableStateOf(false) }
+ LaunchedEffect(membersState) {
+ canSendEvent.value = room.canSendStateEvent(type).getOrElse { false }
+ }
+ return canSendEvent
+ }
+
@Composable
private fun getMemberCount(membersState: MatrixRoomMembersState): State> {
return remember(membersState) {
diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt
index a046548b19..4554e7aec9 100644
--- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt
+++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt
@@ -26,11 +26,12 @@ data class RoomDetailsState(
val roomName: String,
val roomAlias: String?,
val roomAvatarUrl: String?,
- val roomTopic: String?,
+ val roomTopic: RoomTopicState,
val memberCount: Async,
val isEncrypted: Boolean,
val roomType: RoomDetailsType,
val roomMemberDetailsState: RoomMemberDetailsState?,
+ val canEdit: Boolean,
val canInvite: Boolean,
val leaveRoomState: LeaveRoomState,
val eventSink: (RoomDetailsEvent) -> Unit
@@ -40,3 +41,9 @@ sealed interface RoomDetailsType {
object Room : RoomDetailsType
data class Dm(val roomMember: RoomMember) : RoomDetailsType
}
+
+sealed interface RoomTopicState {
+ object Hidden : RoomTopicState
+ object CanAddTopic : RoomTopicState
+ data class ExistingTopic(val topic: String) : RoomTopicState
+}
diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt
index 9cb5b925fa..82fd0ae0aa 100644
--- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt
+++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt
@@ -28,13 +28,15 @@ open class RoomDetailsStateProvider : PreviewParameterProvider
override val values: Sequence
get() = sequenceOf(
aRoomDetailsState(),
- aRoomDetailsState().copy(roomTopic = null),
+ aRoomDetailsState().copy(roomTopic = RoomTopicState.Hidden),
+ aRoomDetailsState().copy(roomTopic = RoomTopicState.CanAddTopic),
aRoomDetailsState().copy(isEncrypted = false),
aRoomDetailsState().copy(roomAlias = null),
aRoomDetailsState().copy(memberCount = Async.Failure(Throwable())),
aDmRoomDetailsState().copy(roomName = "Daniel"),
aDmRoomDetailsState(isDmMemberIgnored = true).copy(roomName = "Daniel"),
aRoomDetailsState().copy(canInvite = true),
+ aRoomDetailsState().copy(canEdit = true),
// Add other state here
)
}
@@ -64,14 +66,17 @@ fun aRoomDetailsState() = RoomDetailsState(
roomName = "Marketing",
roomAlias = "#marketing:domain.com",
roomAvatarUrl = null,
- roomTopic = "Welcome to #marketing, home of the Marketing team " +
- "|| WIKI PAGE: https://domain.org/wiki/Marketing " +
- "|| MAIL iki/Marketing " +
- "|| MAI iki/Marketing " +
- "|| MAI iki/Marketing...",
+ roomTopic = RoomTopicState.ExistingTopic(
+ "Welcome to #marketing, home of the Marketing team " +
+ "|| WIKI PAGE: https://domain.org/wiki/Marketing " +
+ "|| MAIL iki/Marketing " +
+ "|| MAI iki/Marketing " +
+ "|| MAI iki/Marketing..."
+ ),
memberCount = Async.Success(32),
isEncrypted = true,
canInvite = false,
+ canEdit = false,
roomType = RoomDetailsType.Room,
roomMemberDetailsState = null,
leaveRoomState = LeaveRoomState(),
diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt
index 78c26c4ae5..491c1eb22b 100644
--- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt
+++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt
@@ -28,16 +28,25 @@ 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.widthIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.MoreVert
+import androidx.compose.material.icons.outlined.Add
import androidx.compose.material.icons.outlined.Lock
import androidx.compose.material.icons.outlined.Person
import androidx.compose.material.icons.outlined.PersonAddAlt
import androidx.compose.material.icons.outlined.Share
+import androidx.compose.material3.DropdownMenu
+import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
@@ -63,24 +72,26 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.LargeHeightPreview
import io.element.android.libraries.designsystem.theme.LocalColors
+import io.element.android.libraries.designsystem.theme.components.Icon
+import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.ui.strings.R as StringR
-@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
+@OptIn(ExperimentalLayoutApi::class)
@Composable
fun RoomDetailsView(
state: RoomDetailsState,
goBack: () -> Unit,
+ onActionClicked: (RoomDetailsAction) -> Unit,
onShareRoom: () -> Unit,
onShareMember: (RoomMember) -> Unit,
openRoomMemberList: () -> Unit,
invitePeople: () -> Unit,
modifier: Modifier = Modifier,
) {
-
fun onShareMember() {
onShareMember((state.roomType as RoomDetailsType.Dm).roomMember)
}
@@ -88,13 +99,18 @@ fun RoomDetailsView(
Scaffold(
modifier = modifier,
topBar = {
- TopAppBar(title = { }, navigationIcon = { BackButton(onClick = goBack) })
+ RoomDetailsTopBar(
+ goBack = goBack,
+ showEdit = state.canEdit,
+ onActionClicked = onActionClicked
+ )
},
) { padding ->
- Column(modifier = Modifier
- .padding(padding)
- .consumeWindowInsets(padding)
- .verticalScroll(rememberScrollState())
+ Column(
+ modifier = Modifier
+ .padding(padding)
+ .verticalScroll(rememberScrollState())
+ .consumeWindowInsets(padding)
) {
LeaveRoomView(state = state.leaveRoomState)
@@ -108,6 +124,7 @@ fun RoomDetailsView(
)
MainActionsSection(onShareRoom = onShareRoom)
}
+
is RoomDetailsType.Dm -> {
val member = state.roomType.roomMember
RoomMemberHeaderSection(
@@ -120,8 +137,11 @@ fun RoomDetailsView(
}
Spacer(Modifier.height(26.dp))
- if (state.roomTopic != null) {
- TopicSection(roomTopic = state.roomTopic)
+ if (state.roomTopic !is RoomTopicState.Hidden) {
+ TopicSection(
+ roomTopic = state.roomTopic,
+ onActionClicked = onActionClicked,
+ )
}
if (state.roomType is RoomDetailsType.Room) {
@@ -129,10 +149,14 @@ fun RoomDetailsView(
MembersSection(
memberCount = memberCount,
isLoading = state.memberCount.isLoading(),
- showInvite = state.canInvite,
openRoomMemberList = openRoomMemberList,
- invitePeople = invitePeople,
)
+
+ if (state.canInvite) {
+ InviteSection(
+ invitePeople = invitePeople
+ )
+ }
}
if (state.isEncrypted) {
@@ -152,6 +176,45 @@ fun RoomDetailsView(
}
}
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+internal fun RoomDetailsTopBar(
+ goBack: () -> Unit,
+ onActionClicked: (RoomDetailsAction) -> Unit,
+ showEdit: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ var showMenu by remember { mutableStateOf(false) }
+
+ TopAppBar(
+ modifier = modifier,
+ title = { },
+ navigationIcon = { BackButton(onClick = goBack) },
+ actions = {
+ if (showEdit) {
+ IconButton(onClick = { showMenu = !showMenu }) {
+ Icon(Icons.Default.MoreVert, "")
+ }
+ DropdownMenu(
+ modifier = Modifier.widthIn(200.dp),
+ expanded = showMenu,
+ onDismissRequest = { showMenu = false },
+ ) {
+ DropdownMenuItem(
+ text = { Text(stringResource(id = StringR.string.action_edit)) },
+ onClick = {
+ // Explicitly close the menu before handling the action, as otherwise it stays open during the
+ // transition and renders really badly.
+ showMenu = false
+ onActionClicked(RoomDetailsAction.Edit)
+ },
+ )
+ }
+ }
+ },
+ )
+}
+
@Composable
internal fun MainActionsSection(onShareRoom: () -> Unit, modifier: Modifier = Modifier) {
Row(modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
@@ -185,14 +248,26 @@ internal fun RoomHeaderSection(
}
@Composable
-internal fun TopicSection(roomTopic: String, modifier: Modifier = Modifier) {
+internal fun TopicSection(
+ roomTopic: RoomTopicState,
+ onActionClicked: (RoomDetailsAction) -> Unit,
+ modifier: Modifier = Modifier
+) {
PreferenceCategory(title = stringResource(StringR.string.common_topic), modifier = modifier) {
- Text(
- roomTopic,
- modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 12.dp),
- style = MaterialTheme.typography.bodySmall,
- color = MaterialTheme.colorScheme.tertiary
- )
+ if (roomTopic is RoomTopicState.CanAddTopic) {
+ PreferenceText(
+ title = stringResource(R.string.screen_room_details_add_topic_title),
+ icon = Icons.Outlined.Add,
+ onClick = { onActionClicked(RoomDetailsAction.AddTopic) },
+ )
+ } else if (roomTopic is RoomTopicState.ExistingTopic) {
+ Text(
+ roomTopic.topic,
+ modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 12.dp),
+ style = MaterialTheme.typography.bodySmall,
+ color = MaterialTheme.colorScheme.tertiary
+ )
+ }
}
}
@@ -200,8 +275,6 @@ internal fun TopicSection(roomTopic: String, modifier: Modifier = Modifier) {
internal fun MembersSection(
memberCount: Int?,
isLoading: Boolean,
- showInvite: Boolean,
- invitePeople: () -> Unit,
openRoomMemberList: () -> Unit,
modifier: Modifier = Modifier,
) {
@@ -213,13 +286,20 @@ internal fun MembersSection(
onClick = openRoomMemberList,
loadingCurrentValue = isLoading,
)
- if (showInvite) {
- PreferenceText(
- title = stringResource(R.string.screen_room_details_invite_people_title),
- icon = Icons.Outlined.PersonAddAlt,
- onClick = invitePeople,
- )
- }
+ }
+}
+
+@Composable
+internal fun InviteSection(
+ invitePeople: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ PreferenceCategory(modifier = modifier) {
+ PreferenceText(
+ title = stringResource(R.string.screen_room_details_invite_people_title),
+ icon = Icons.Outlined.PersonAddAlt,
+ onClick = invitePeople,
+ )
}
}
@@ -261,6 +341,7 @@ private fun ContentToPreview(state: RoomDetailsState) {
RoomDetailsView(
state = state,
goBack = {},
+ onActionClicked = {},
onShareRoom = {},
onShareMember = {},
openRoomMemberList = {},
diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditEvents.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditEvents.kt
new file mode 100644
index 0000000000..b4bc348b8a
--- /dev/null
+++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditEvents.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.features.roomdetails.impl.edit
+
+import io.element.android.libraries.matrix.ui.media.AvatarAction
+
+sealed interface RoomDetailsEditEvents {
+ data class HandleAvatarAction(val action: AvatarAction) : RoomDetailsEditEvents
+ data class UpdateRoomName(val name: String) : RoomDetailsEditEvents
+ data class UpdateRoomTopic(val topic: String) : RoomDetailsEditEvents
+ object Save : RoomDetailsEditEvents
+ object CancelSaveChanges : RoomDetailsEditEvents
+}
diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditNode.kt
new file mode 100644
index 0000000000..af05b547b2
--- /dev/null
+++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditNode.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.features.roomdetails.impl.edit
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.bumble.appyx.core.modality.BuildContext
+import com.bumble.appyx.core.node.Node
+import com.bumble.appyx.core.plugin.Plugin
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+import io.element.android.anvilannotations.ContributesNode
+import io.element.android.libraries.di.RoomScope
+
+@ContributesNode(RoomScope::class)
+class RoomDetailsEditNode @AssistedInject constructor(
+ @Assisted buildContext: BuildContext,
+ @Assisted plugins: List,
+ private val presenter: RoomDetailsEditPresenter,
+) : Node(buildContext, plugins = plugins) {
+
+ @Composable
+ override fun View(modifier: Modifier) {
+ val state = presenter.present()
+ RoomDetailsEditView(
+ state = state,
+ onBackPressed = ::navigateUp,
+ onRoomEdited = ::navigateUp,
+ modifier = modifier,
+ )
+ }
+}
diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenter.kt
new file mode 100644
index 0000000000..a6726f9105
--- /dev/null
+++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenter.kt
@@ -0,0 +1,166 @@
+/*
+ * 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.features.roomdetails.impl.edit
+
+import android.net.Uri
+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
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.core.net.toUri
+import io.element.android.libraries.matrix.ui.media.AvatarAction
+import io.element.android.libraries.architecture.Async
+import io.element.android.libraries.architecture.Presenter
+import io.element.android.libraries.architecture.execute
+import io.element.android.libraries.core.mimetype.MimeTypes
+import io.element.android.libraries.matrix.api.room.MatrixRoom
+import io.element.android.libraries.matrix.api.room.StateEventType
+import io.element.android.libraries.mediapickers.api.PickerProvider
+import io.element.android.libraries.mediaupload.api.MediaPreProcessor
+import io.element.android.libraries.mediaupload.api.MediaUploadInfo
+import kotlinx.collections.immutable.toImmutableList
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+class RoomDetailsEditPresenter @Inject constructor(
+ private val room: MatrixRoom,
+ private val mediaPickerProvider: PickerProvider,
+ private val mediaPreProcessor: MediaPreProcessor,
+) : Presenter {
+
+ @Composable
+ override fun present(): RoomDetailsEditState {
+ val roomSyncUpdateFlow = room.syncUpdateFlow().collectAsState(0L)
+
+ // Since there is no way to obtain the new avatar uri after uploading a new avatar,
+ // just erase the local value when the room field has changed
+ var roomAvatarUri by rememberSaveable(room.avatarUrl) { mutableStateOf(room.avatarUrl?.toUri()) }
+
+ var roomName by rememberSaveable { mutableStateOf((room.name ?: room.displayName).trim()) }
+ var roomTopic by rememberSaveable { mutableStateOf(room.topic?.trim()) }
+
+ val saveButtonEnabled by remember(
+ roomSyncUpdateFlow.value,
+ roomName,
+ roomTopic,
+ roomAvatarUri,
+ ) {
+ derivedStateOf {
+ roomAvatarUri?.toString()?.trim() != room.avatarUrl?.toUri()?.toString()?.trim()
+ || roomName.trim() != (room.name ?: room.displayName).trim()
+ || roomTopic.orEmpty().trim() != room.topic.orEmpty().trim()
+ }
+ }
+
+ var canChangeName by remember { mutableStateOf(false) }
+ var canChangeTopic by remember { mutableStateOf(false) }
+ var canChangeAvatar by remember { mutableStateOf(false) }
+
+ LaunchedEffect(Unit) {
+ canChangeName = room.canSendStateEvent(StateEventType.ROOM_NAME).getOrElse { false }
+ canChangeTopic = room.canSendStateEvent(StateEventType.ROOM_TOPIC).getOrElse { false }
+ canChangeAvatar = room.canSendStateEvent(StateEventType.ROOM_AVATAR).getOrElse { false }
+ }
+
+ val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker(
+ onResult = { uri -> if (uri != null) roomAvatarUri = uri }
+ )
+ val galleryImagePicker = mediaPickerProvider.registerGalleryImagePicker(
+ onResult = { uri -> if (uri != null) roomAvatarUri = uri }
+ )
+
+ val avatarActions by remember(roomAvatarUri) {
+ derivedStateOf {
+ listOfNotNull(
+ AvatarAction.TakePhoto,
+ AvatarAction.ChoosePhoto,
+ AvatarAction.Remove.takeIf { roomAvatarUri != null },
+ ).toImmutableList()
+ }
+ }
+
+ val saveAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) }
+ val localCoroutineScope = rememberCoroutineScope()
+ fun handleEvents(event: RoomDetailsEditEvents) {
+ when (event) {
+ is RoomDetailsEditEvents.Save -> localCoroutineScope.saveChanges(roomName, roomTopic, roomAvatarUri, saveAction)
+ is RoomDetailsEditEvents.HandleAvatarAction -> {
+ when (event.action) {
+ AvatarAction.ChoosePhoto -> galleryImagePicker.launch()
+ AvatarAction.TakePhoto -> cameraPhotoPicker.launch()
+ AvatarAction.Remove -> roomAvatarUri = null
+ }
+ }
+
+ is RoomDetailsEditEvents.UpdateRoomName -> roomName = event.name
+ is RoomDetailsEditEvents.UpdateRoomTopic -> roomTopic = event.topic.takeUnless { it.isEmpty() }
+ RoomDetailsEditEvents.CancelSaveChanges -> saveAction.value = Async.Uninitialized
+ }
+ }
+
+ return RoomDetailsEditState(
+ roomId = room.roomId.value,
+ roomName = roomName,
+ canChangeName = canChangeName,
+ roomTopic = roomTopic.orEmpty(),
+ canChangeTopic = canChangeTopic,
+ roomAvatarUrl = roomAvatarUri,
+ canChangeAvatar = canChangeAvatar,
+ avatarActions = avatarActions,
+ saveButtonEnabled = saveButtonEnabled,
+ saveAction = saveAction.value,
+ eventSink = ::handleEvents,
+ )
+ }
+
+ private fun CoroutineScope.saveChanges(name: String, topic: String?, avatarUri: Uri?, action: MutableState>) = launch {
+ val results = mutableListOf>()
+ suspend {
+ if (topic.orEmpty().trim() != room.topic.orEmpty().trim()) {
+ results.add(room.setTopic(topic.orEmpty()))
+ }
+ if (name.isNotEmpty() && name.trim() != room.name.orEmpty().trim()) {
+ results.add(room.setName(name))
+ }
+ if (avatarUri?.toString()?.trim() != room.avatarUrl?.trim()) {
+ results.add(updateAvatar(avatarUri))
+ }
+ if (results.all { it.isSuccess }) Unit else results.first { it.isFailure }.getOrThrow()
+ }.execute(action)
+ }
+
+ private suspend fun updateAvatar(avatarUri: Uri?): Result {
+ return runCatching {
+ val result = if (avatarUri != null) {
+ val preprocessed = mediaPreProcessor.process(avatarUri, MimeTypes.Jpeg, compressIfPossible = false).getOrThrow() as? MediaUploadInfo.Image
+ val byteArray = preprocessed?.file?.readBytes()
+ byteArray?.let { room.updateAvatar(MimeTypes.Jpeg, it) } ?: error("Could not process the given uri ($avatarUri)")
+ } else {
+ room.removeAvatar()
+ }
+ result.getOrThrow()
+ }
+ }
+}
diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditState.kt
new file mode 100644
index 0000000000..ceb87b6f27
--- /dev/null
+++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditState.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.features.roomdetails.impl.edit
+
+import android.net.Uri
+import io.element.android.libraries.matrix.ui.media.AvatarAction
+import io.element.android.libraries.architecture.Async
+import kotlinx.collections.immutable.ImmutableList
+
+data class RoomDetailsEditState(
+ val roomId: String,
+ val roomName: String,
+ val canChangeName: Boolean,
+ val roomTopic: String,
+ val canChangeTopic: Boolean,
+ val roomAvatarUrl: Uri?,
+ val canChangeAvatar: Boolean,
+ val avatarActions: ImmutableList,
+ val saveButtonEnabled: Boolean,
+ val saveAction: Async,
+ val eventSink: (RoomDetailsEditEvents) -> Unit
+)
diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditStateProvider.kt
new file mode 100644
index 0000000000..62a363aeac
--- /dev/null
+++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditStateProvider.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.features.roomdetails.impl.edit
+
+import android.net.Uri
+import androidx.compose.ui.tooling.preview.PreviewParameterProvider
+import io.element.android.libraries.architecture.Async
+import kotlinx.collections.immutable.persistentListOf
+
+open class RoomDetailsEditStateProvider : PreviewParameterProvider {
+ override val values: Sequence
+ get() = sequenceOf(
+ aRoomDetailsEditState(),
+ aRoomDetailsEditState().copy(roomTopic = ""),
+ aRoomDetailsEditState().copy(roomAvatarUrl = Uri.EMPTY),
+ aRoomDetailsEditState().copy(canChangeName = true, canChangeTopic = false, canChangeAvatar = true, saveButtonEnabled = false),
+ aRoomDetailsEditState().copy(canChangeName = false, canChangeTopic = true, canChangeAvatar = false, saveButtonEnabled = false),
+ aRoomDetailsEditState().copy(saveAction = Async.Loading()),
+ aRoomDetailsEditState().copy(saveAction = Async.Failure(Throwable("Whelp")))
+ )
+}
+
+fun aRoomDetailsEditState() = RoomDetailsEditState(
+ roomId = "a room id",
+ roomName = "Marketing",
+ canChangeName = true,
+ roomTopic = "a room topic that is quite long so should wrap onto multiple lines",
+ canChangeTopic = true,
+ roomAvatarUrl = null,
+ canChangeAvatar = true,
+ avatarActions = persistentListOf(),
+ saveButtonEnabled = true,
+ saveAction = Async.Uninitialized,
+ eventSink = {}
+)
diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditView.kt
new file mode 100644
index 0000000000..414eaf9831
--- /dev/null
+++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditView.kt
@@ -0,0 +1,306 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
+
+package io.element.android.features.roomdetails.impl.edit
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+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.rememberScrollState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.ModalBottomSheetValue
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AddAPhoto
+import androidx.compose.material.rememberModalBottomSheetState
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.focus.FocusManager
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import io.element.android.features.roomdetails.impl.R
+import io.element.android.libraries.architecture.Async
+import io.element.android.libraries.designsystem.components.LabelledTextField
+import io.element.android.libraries.designsystem.components.ProgressDialog
+import io.element.android.libraries.designsystem.components.avatar.Avatar
+import io.element.android.libraries.designsystem.components.avatar.AvatarData
+import io.element.android.libraries.designsystem.components.avatar.AvatarSize
+import io.element.android.libraries.designsystem.components.button.BackButton
+import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
+import io.element.android.libraries.designsystem.preview.ElementPreviewDark
+import io.element.android.libraries.designsystem.preview.ElementPreviewLight
+import io.element.android.libraries.designsystem.theme.LocalColors
+import io.element.android.libraries.designsystem.theme.components.CenterAlignedTopAppBar
+import io.element.android.libraries.designsystem.theme.components.Icon
+import io.element.android.libraries.designsystem.theme.components.Scaffold
+import io.element.android.libraries.designsystem.theme.components.Text
+import io.element.android.libraries.designsystem.theme.components.TextButton
+import io.element.android.libraries.matrix.ui.components.AvatarActionBottomSheet
+import io.element.android.libraries.matrix.ui.components.UnsavedAvatar
+import kotlinx.coroutines.launch
+import io.element.android.libraries.ui.strings.R as StringR
+
+@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
+@Composable
+fun RoomDetailsEditView(
+ state: RoomDetailsEditState,
+ onBackPressed: () -> Unit,
+ onRoomEdited: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ val coroutineScope = rememberCoroutineScope()
+ val focusManager = LocalFocusManager.current
+ val itemActionsBottomSheetState = rememberModalBottomSheetState(
+ initialValue = ModalBottomSheetValue.Hidden,
+ )
+
+ fun onAvatarClicked() {
+ focusManager.clearFocus()
+ coroutineScope.launch {
+ itemActionsBottomSheetState.show()
+ }
+ }
+
+ Scaffold(
+ modifier = modifier.clearFocusOnTap(focusManager),
+ topBar = {
+ CenterAlignedTopAppBar(
+ title = {
+ Text(
+ text = stringResource(id = R.string.screen_room_details_edit_room_title),
+ fontSize = 16.sp,
+ fontWeight = FontWeight.SemiBold,
+ )
+ },
+ navigationIcon = { BackButton(onClick = onBackPressed) },
+ actions = {
+ TextButton(
+ enabled = state.saveButtonEnabled,
+ onClick = {
+ focusManager.clearFocus()
+ state.eventSink(RoomDetailsEditEvents.Save)
+ },
+ ) {
+ Text(
+ text = stringResource(StringR.string.action_save),
+ fontSize = 16.sp,
+ )
+ }
+ }
+ )
+ },
+ ) { padding ->
+ Column(
+ modifier = Modifier
+ .padding(padding)
+ .padding(horizontal = 16.dp)
+ .navigationBarsPadding()
+ .imePadding()
+ .verticalScroll(rememberScrollState())
+ ) {
+ Spacer(modifier = Modifier.height(24.dp))
+ EditableAvatarView(state, ::onAvatarClicked)
+ Spacer(modifier = Modifier.height(60.dp))
+
+ if (state.canChangeName) {
+ LabelledTextField(
+ label = stringResource(id = R.string.screen_room_details_room_name_label),
+ value = state.roomName,
+ placeholder = stringResource(id = R.string.screen_room_details_room_name_placeholder),
+ singleLine = true,
+ onValueChange = { state.eventSink(RoomDetailsEditEvents.UpdateRoomName(it)) },
+ )
+ } else {
+ LabelledReadOnlyField(
+ title = stringResource(R.string.screen_room_details_room_name_label),
+ value = state.roomName
+ )
+ }
+
+ Spacer(modifier = Modifier.height(28.dp))
+
+ if (state.canChangeTopic) {
+ LabelledTextField(
+ label = stringResource(id = StringR.string.common_topic),
+ value = state.roomTopic,
+ placeholder = stringResource(id = R.string.screen_room_details_topic_placeholder),
+ maxLines = 10,
+ onValueChange = { state.eventSink(RoomDetailsEditEvents.UpdateRoomTopic(it)) },
+ )
+ } else {
+ LabelledReadOnlyField(
+ title = stringResource(R.string.screen_room_details_topic_title),
+ value = state.roomTopic
+ )
+ }
+ }
+ }
+
+ AvatarActionBottomSheet(
+ actions = state.avatarActions,
+ modalBottomSheetState = itemActionsBottomSheetState,
+ onActionSelected = { state.eventSink(RoomDetailsEditEvents.HandleAvatarAction(it)) }
+ )
+
+ when (state.saveAction) {
+ is Async.Loading -> {
+ ProgressDialog(text = stringResource(R.string.screen_room_details_updating_room))
+ }
+
+ is Async.Failure -> {
+ ErrorDialog(
+ content = stringResource(R.string.screen_room_details_edition_error),
+ onDismiss = { state.eventSink(RoomDetailsEditEvents.CancelSaveChanges) },
+ )
+ }
+
+ is Async.Success -> {
+ LaunchedEffect(state.saveAction) {
+ onRoomEdited()
+ }
+ }
+
+ else -> Unit
+ }
+}
+
+@Composable
+private fun EditableAvatarView(
+ state: RoomDetailsEditState,
+ onAvatarClicked: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ Column(modifier = modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
+ Box(
+ modifier = Modifier
+ .size(70.dp)
+ .clickable(onClick = onAvatarClicked, enabled = state.canChangeAvatar)
+ ) {
+ // TODO this might be able to be simplified into a single component once send/receive media is done
+ when (state.roomAvatarUrl?.scheme) {
+ null, "mxc" -> {
+ Avatar(
+ avatarData = AvatarData(state.roomId, state.roomName, state.roomAvatarUrl?.toString(), size = AvatarSize.HUGE),
+ modifier = Modifier.fillMaxSize(),
+ )
+ }
+
+ else -> {
+ UnsavedAvatar(
+ avatarUri = state.roomAvatarUrl,
+ modifier = Modifier.fillMaxSize(),
+ )
+ }
+ }
+
+ if (state.canChangeAvatar) {
+ Box(
+ modifier = Modifier
+ .align(Alignment.BottomEnd)
+ .clip(CircleShape)
+ .background(LocalColors.current.gray1400)
+ .size(24.dp),
+ contentAlignment = Alignment.Center,
+ ) {
+ Icon(
+ modifier = Modifier.size(16.dp),
+ imageVector = Icons.Outlined.AddAPhoto,
+ contentDescription = "",
+ tint = MaterialTheme.colorScheme.onPrimary,
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun LabelledReadOnlyField(
+ title: String,
+ value: String,
+ modifier: Modifier = Modifier
+) {
+ Column(
+ modifier = modifier,
+ verticalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ Text(
+ modifier = Modifier.padding(horizontal = 16.dp),
+ style = MaterialTheme.typography.titleSmall,
+ color = MaterialTheme.colorScheme.primary,
+ text = title,
+ )
+
+ Text(
+ modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 12.dp),
+ style = MaterialTheme.typography.bodyLarge,
+ color = MaterialTheme.colorScheme.primary,
+ text = value,
+ )
+ }
+}
+
+private fun Modifier.clearFocusOnTap(focusManager: FocusManager): Modifier =
+ pointerInput(Unit) {
+ detectTapGestures(onTap = {
+ focusManager.clearFocus()
+ })
+ }
+
+@Preview
+@Composable
+fun RoomDetailsEditViewLightPreview(@PreviewParameter(RoomDetailsEditStateProvider::class) state: RoomDetailsEditState) =
+ ElementPreviewLight { ContentToPreview(state) }
+
+@Preview
+@Composable
+fun RoomDetailsEditViewDarkPreview(@PreviewParameter(RoomDetailsEditStateProvider::class) state: RoomDetailsEditState) =
+ ElementPreviewDark { ContentToPreview(state) }
+
+@Composable
+private fun ContentToPreview(state: RoomDetailsEditState) {
+ RoomDetailsEditView(
+ state = state,
+ onBackPressed = {},
+ onRoomEdited = {},
+ )
+}
diff --git a/features/roomdetails/impl/src/main/res/values/localazy.xml b/features/roomdetails/impl/src/main/res/values/localazy.xml
index ec907c6078..4f45bb6b49 100644
--- a/features/roomdetails/impl/src/main/res/values/localazy.xml
+++ b/features/roomdetails/impl/src/main/res/values/localazy.xml
@@ -12,7 +12,10 @@
"Unable to update room"
"Messages are secured with locks. Only you and the recipients have the unique keys to unlock them."
"Message encryption enabled"
+ "Room name"
+ "e.g. Product Sprint"
"Share room"
+ "What is this room about?"
"Updating room…"
"Pending"
"Room members"
@@ -22,7 +25,7 @@
"Unblock"
"On unblocking the user, you will be able to see all messages by them again."
"Unblock user"
- "Invite people"
+ "Invite friends to Element"
"Leave room"
"People"
"Security"
diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt
index 0c950071ca..bcaa824104 100644
--- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt
+++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt
@@ -19,10 +19,11 @@ package io.element.android.features.roomdetails
import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
-import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
import io.element.android.features.leaveroom.fake.LeaveRoomPresenterFake
import io.element.android.features.roomdetails.impl.RoomDetailsPresenter
import io.element.android.features.roomdetails.impl.RoomDetailsType
+import io.element.android.features.roomdetails.impl.RoomTopicState
import io.element.android.features.roomdetails.impl.members.aRoomMember
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter
import io.element.android.libraries.architecture.Async
@@ -31,8 +32,8 @@ import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
-import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.room.RoomMembershipState
+import io.element.android.libraries.matrix.api.room.StateEventType
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
@@ -40,7 +41,6 @@ import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.A_USER_ID_2
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
-import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -48,9 +48,6 @@ import org.junit.Test
@ExperimentalCoroutinesApi
class RoomDetailsPresenterTests {
- private val roomMembershipObserver = RoomMembershipObserver()
- private val testCoroutineDispatchers = testCoroutineDispatchers()
-
private fun aRoomDetailsPresenter(room: MatrixRoom): RoomDetailsPresenter {
val roomMemberDetailsPresenterFactory = object : RoomMemberDetailsPresenter.Factory {
override fun create(roomMemberId: UserId): RoomMemberDetailsPresenter {
@@ -68,12 +65,12 @@ class RoomDetailsPresenterTests {
presenter.present()
}.test {
val initialState = awaitItem()
- Truth.assertThat(initialState.roomId).isEqualTo(room.roomId.value)
- Truth.assertThat(initialState.roomName).isEqualTo(room.name)
- Truth.assertThat(initialState.roomAvatarUrl).isEqualTo(room.avatarUrl)
- Truth.assertThat(initialState.roomTopic).isEqualTo(room.topic)
- Truth.assertThat(initialState.memberCount).isEqualTo(Async.Uninitialized)
- Truth.assertThat(initialState.isEncrypted).isEqualTo(room.isEncrypted)
+ assertThat(initialState.roomId).isEqualTo(room.roomId.value)
+ assertThat(initialState.roomName).isEqualTo(room.name)
+ assertThat(initialState.roomAvatarUrl).isEqualTo(room.avatarUrl)
+ assertThat(initialState.roomTopic).isEqualTo(RoomTopicState.ExistingTopic(room.topic!!))
+ assertThat(initialState.memberCount).isEqualTo(Async.Uninitialized)
+ assertThat(initialState.isEncrypted).isEqualTo(room.isEncrypted)
cancelAndIgnoreRemainingEvents()
}
@@ -93,22 +90,22 @@ class RoomDetailsPresenterTests {
}.test {
room.givenRoomMembersState(MatrixRoomMembersState.Unknown)
val initialState = awaitItem()
- Truth.assertThat(initialState.memberCount).isEqualTo(Async.Uninitialized)
+ assertThat(initialState.memberCount).isEqualTo(Async.Uninitialized)
skipItems(1)
room.givenRoomMembersState(MatrixRoomMembersState.Pending(null))
val loadingState = awaitItem()
- Truth.assertThat(loadingState.memberCount).isEqualTo(Async.Loading(null))
+ assertThat(loadingState.memberCount).isEqualTo(Async.Loading(null))
room.givenRoomMembersState(MatrixRoomMembersState.Error(error))
skipItems(1)
val failureState = awaitItem()
- Truth.assertThat(failureState.memberCount).isEqualTo(Async.Failure(error, null))
+ assertThat(failureState.memberCount).isEqualTo(Async.Failure(error, null))
room.givenRoomMembersState(MatrixRoomMembersState.Ready(roomMembers))
skipItems(1)
val successState = awaitItem()
- Truth.assertThat(successState.memberCount).isEqualTo(Async.Success(1))
+ assertThat(successState.memberCount).isEqualTo(Async.Success(1))
cancelAndIgnoreRemainingEvents()
}
@@ -122,7 +119,7 @@ class RoomDetailsPresenterTests {
presenter.present()
}.test {
val initialState = awaitItem()
- Truth.assertThat(initialState.roomName).isEqualTo(room.displayName)
+ assertThat(initialState.roomName).isEqualTo(room.displayName)
cancelAndIgnoreRemainingEvents()
}
@@ -144,7 +141,7 @@ class RoomDetailsPresenterTests {
presenter.present()
}.test {
val initialState = awaitItem()
- Truth.assertThat(initialState.roomType).isEqualTo(RoomDetailsType.Dm(otherRoomMember))
+ assertThat(initialState.roomType).isEqualTo(RoomDetailsType.Dm(otherRoomMember))
cancelAndIgnoreRemainingEvents()
}
@@ -160,9 +157,9 @@ class RoomDetailsPresenterTests {
presenter.present()
}.test {
// Initially false
- Truth.assertThat(awaitItem().canInvite).isFalse()
+ assertThat(awaitItem().canInvite).isFalse()
// Then the asynchronous check completes and it becomes true
- Truth.assertThat(awaitItem().canInvite).isTrue()
+ assertThat(awaitItem().canInvite).isTrue()
cancelAndIgnoreRemainingEvents()
}
@@ -177,7 +174,7 @@ class RoomDetailsPresenterTests {
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
- Truth.assertThat(awaitItem().canInvite).isFalse()
+ assertThat(awaitItem().canInvite).isFalse()
}
}
@@ -190,7 +187,103 @@ class RoomDetailsPresenterTests {
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
- Truth.assertThat(awaitItem().canInvite).isFalse()
+ assertThat(awaitItem().canInvite).isFalse()
+ }
+ }
+
+ @Test
+ fun `present - initial state when user can edit one attribute`() = runTest {
+ val room = aMatrixRoom().apply {
+ givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true))
+ givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(false))
+ givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.failure(Throwable("Whelp")))
+ givenCanInviteResult(Result.success(false))
+ }
+ val presenter = aRoomDetailsPresenter(room)
+ moleculeFlow(RecompositionClock.Immediate) {
+ presenter.present()
+ }.test {
+ // Initially false
+ assertThat(awaitItem().canEdit).isFalse()
+ // Then the asynchronous check completes and it becomes true
+ assertThat(awaitItem().canEdit).isTrue()
+
+ cancelAndIgnoreRemainingEvents()
+ }
+ }
+
+ @Test
+ fun `present - initial state when user can edit all attributes`() = runTest {
+ val room = aMatrixRoom().apply {
+ givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true))
+ givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(true))
+ givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(true))
+ givenCanInviteResult(Result.success(false))
+ }
+ val presenter = aRoomDetailsPresenter(room)
+ moleculeFlow(RecompositionClock.Immediate) {
+ presenter.present()
+ }.test {
+ // Initially false
+ assertThat(awaitItem().canEdit).isFalse()
+ // Then the asynchronous check completes and it becomes true
+ assertThat(awaitItem().canEdit).isTrue()
+
+ cancelAndIgnoreRemainingEvents()
+ }
+ }
+
+ @Test
+ fun `present - initial state when user can edit no attributes`() = runTest {
+ val room = aMatrixRoom().apply {
+ givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(false))
+ givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(false))
+ givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(false))
+ givenCanInviteResult(Result.success(false))
+ }
+ val presenter = aRoomDetailsPresenter(room)
+ moleculeFlow(RecompositionClock.Immediate) {
+ presenter.present()
+ }.test {
+ // Initially false, and no further events
+ assertThat(awaitItem().canEdit).isFalse()
+ }
+ }
+
+ @Test
+ fun `present - topic state is hidden when no topic and user has no permission`() = runTest {
+ val room = aMatrixRoom(topic = null).apply {
+ givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(false))
+ givenCanInviteResult(Result.success(false))
+ }
+
+ val presenter = aRoomDetailsPresenter(room)
+ moleculeFlow(RecompositionClock.Immediate) {
+ presenter.present()
+ }.test {
+ // The initial state is "hidden" and no further state changes happen
+ assertThat(awaitItem().roomTopic).isEqualTo(RoomTopicState.Hidden)
+ }
+ }
+
+ @Test
+ fun `present - topic state is 'can add topic' when no topic and user has permission`() = runTest {
+ val room = aMatrixRoom(topic = null).apply {
+ givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true))
+ givenCanInviteResult(Result.success(false))
+ }
+
+ val presenter = aRoomDetailsPresenter(room)
+ moleculeFlow(RecompositionClock.Immediate) {
+ presenter.present()
+ }.test {
+ // Ignore the initial state
+ skipItems(1)
+
+ // When the async permission check finishes, the topic state will be updated
+ assertThat(awaitItem().roomTopic).isEqualTo(RoomTopicState.CanAddTopic)
+
+ cancelAndIgnoreRemainingEvents()
}
}
}
diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/edit/RoomDetailsEditPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/edit/RoomDetailsEditPresenterTest.kt
new file mode 100644
index 0000000000..adbec76ae7
--- /dev/null
+++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/edit/RoomDetailsEditPresenterTest.kt
@@ -0,0 +1,618 @@
+/*
+ * 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.features.roomdetails.edit
+
+import android.net.Uri
+import app.cash.molecule.RecompositionClock
+import app.cash.molecule.moleculeFlow
+import app.cash.turbine.test
+import com.google.common.truth.Truth.assertThat
+import io.element.android.libraries.matrix.ui.media.AvatarAction
+import io.element.android.features.roomdetails.aMatrixRoom
+import io.element.android.features.roomdetails.impl.edit.RoomDetailsEditEvents
+import io.element.android.features.roomdetails.impl.edit.RoomDetailsEditPresenter
+import io.element.android.libraries.architecture.Async
+import io.element.android.libraries.matrix.api.room.MatrixRoom
+import io.element.android.libraries.matrix.api.room.StateEventType
+import io.element.android.libraries.matrix.test.AN_AVATAR_URL
+import io.element.android.libraries.mediapickers.test.FakePickerProvider
+import io.element.android.libraries.mediaupload.api.MediaUploadInfo
+import io.element.android.libraries.mediaupload.api.ThumbnailProcessingInfo
+import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.mockkStatic
+import io.mockk.unmockkAll
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import java.io.File
+
+@ExperimentalCoroutinesApi
+class RoomDetailsEditPresenterTest {
+
+ private lateinit var fakePickerProvider: FakePickerProvider
+ private lateinit var fakeMediaPreProcessor: FakeMediaPreProcessor
+
+ private val roomAvatarUri: Uri = mockk()
+ private val anotherAvatarUri: Uri = mockk()
+
+ private val fakeFileContents = ByteArray(2)
+
+ @Before
+ fun setup() {
+ fakePickerProvider = FakePickerProvider()
+ fakeMediaPreProcessor = FakeMediaPreProcessor()
+ mockkStatic(Uri::class)
+
+ every { Uri.parse(AN_AVATAR_URL) } returns roomAvatarUri
+ every { Uri.parse(ANOTHER_AVATAR_URL) } returns anotherAvatarUri
+ }
+
+ @After
+ fun tearDown() {
+ unmockkAll()
+ }
+
+ private fun aRoomDetailsEditPresenter(room: MatrixRoom): RoomDetailsEditPresenter {
+ return RoomDetailsEditPresenter(
+ room = room,
+ mediaPickerProvider = fakePickerProvider,
+ mediaPreProcessor = fakeMediaPreProcessor,
+ )
+ }
+
+ @Test
+ fun `present - initial state is created from room info`() = runTest {
+ val room = aMatrixRoom(avatarUrl = AN_AVATAR_URL)
+ val presenter = aRoomDetailsEditPresenter(room)
+
+ moleculeFlow(RecompositionClock.Immediate) {
+ presenter.present()
+ }.test {
+ val initialState = awaitItem()
+ assertThat(initialState.roomId).isEqualTo(room.roomId.value)
+ assertThat(initialState.roomName).isEqualTo(room.name)
+ assertThat(initialState.roomAvatarUrl).isEqualTo(roomAvatarUri)
+ assertThat(initialState.roomTopic).isEqualTo(room.topic.orEmpty())
+ assertThat(initialState.avatarActions).containsExactly(
+ AvatarAction.ChoosePhoto,
+ AvatarAction.TakePhoto,
+ AvatarAction.Remove
+ )
+ assertThat(initialState.saveButtonEnabled).isEqualTo(false)
+ assertThat(initialState.saveAction).isInstanceOf(Async.Uninitialized::class.java)
+ }
+ }
+
+ @Test
+ fun `present - sets canChangeName if user has permission`() = runTest {
+ val room = aMatrixRoom(avatarUrl = AN_AVATAR_URL).apply {
+ givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(true))
+ givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(false))
+ givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.failure(Throwable("Oops"))) }
+ val presenter = aRoomDetailsEditPresenter(room)
+
+ moleculeFlow(RecompositionClock.Immediate) {
+ presenter.present()
+ }.test {
+ // Initially false
+ val initialState = awaitItem()
+ assertThat(initialState.canChangeName).isFalse()
+ assertThat(initialState.canChangeAvatar).isFalse()
+ assertThat(initialState.canChangeTopic).isFalse()
+
+ // When the asynchronous check completes, the single field we can edit is true
+ val settledState = awaitItem()
+ assertThat(settledState.canChangeName).isTrue()
+ assertThat(settledState.canChangeAvatar).isFalse()
+ assertThat(settledState.canChangeTopic).isFalse()
+ }
+ }
+
+ @Test
+ fun `present - sets canChangeAvatar if user has permission`() = runTest {
+ val room = aMatrixRoom(avatarUrl = AN_AVATAR_URL).apply {
+ givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(false))
+ givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(true))
+ givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.failure(Throwable("Oops")))
+ }
+ val presenter = aRoomDetailsEditPresenter(room)
+
+ moleculeFlow(RecompositionClock.Immediate) {
+ presenter.present()
+ }.test {
+ // Initially false
+ val initialState = awaitItem()
+ assertThat(initialState.canChangeName).isFalse()
+ assertThat(initialState.canChangeAvatar).isFalse()
+ assertThat(initialState.canChangeTopic).isFalse()
+
+ // When the asynchronous check completes, the single field we can edit is true
+ val settledState = awaitItem()
+ assertThat(settledState.canChangeName).isFalse()
+ assertThat(settledState.canChangeAvatar).isTrue()
+ assertThat(settledState.canChangeTopic).isFalse()
+ }
+ }
+
+ @Test
+ fun `present - sets canChangeTopic if user has permission`() = runTest {
+ val room = aMatrixRoom(avatarUrl = AN_AVATAR_URL).apply {
+ givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(false))
+ givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.failure(Throwable("Oops")))
+ givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true))
+ }
+ val presenter = aRoomDetailsEditPresenter(room)
+
+ moleculeFlow(RecompositionClock.Immediate) {
+ presenter.present()
+ }.test {
+ // Initially false
+ val initialState = awaitItem()
+ assertThat(initialState.canChangeName).isFalse()
+ assertThat(initialState.canChangeAvatar).isFalse()
+ assertThat(initialState.canChangeTopic).isFalse()
+
+ // When the asynchronous check completes, the single field we can edit is true
+ val settledState = awaitItem()
+ assertThat(settledState.canChangeName).isFalse()
+ assertThat(settledState.canChangeAvatar).isFalse()
+ assertThat(settledState.canChangeTopic).isTrue()
+ }
+ }
+
+ @Test
+ fun `present - updates state in response to changes`() = runTest {
+ val room = aMatrixRoom(topic = "My topic", name = "Name", avatarUrl = AN_AVATAR_URL)
+ val presenter = aRoomDetailsEditPresenter(room)
+
+ moleculeFlow(RecompositionClock.Immediate) {
+ presenter.present()
+ }.test {
+ val initialState = awaitItem()
+ assertThat(initialState.roomTopic).isEqualTo("My topic")
+ assertThat(initialState.roomName).isEqualTo("Name")
+ assertThat(initialState.roomAvatarUrl).isEqualTo(roomAvatarUri)
+
+ initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName("Name II"))
+ awaitItem().apply {
+ assertThat(roomTopic).isEqualTo("My topic")
+ assertThat(roomName).isEqualTo("Name II")
+ assertThat(roomAvatarUrl).isEqualTo(roomAvatarUri)
+ }
+
+ initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName("Name III"))
+ awaitItem().apply {
+ assertThat(roomTopic).isEqualTo("My topic")
+ assertThat(roomName).isEqualTo("Name III")
+ assertThat(roomAvatarUrl).isEqualTo(roomAvatarUri)
+ }
+
+ initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic("Another topic"))
+ awaitItem().apply {
+ assertThat(roomTopic).isEqualTo("Another topic")
+ assertThat(roomName).isEqualTo("Name III")
+ assertThat(roomAvatarUrl).isEqualTo(roomAvatarUri)
+ }
+
+ initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.Remove))
+ awaitItem().apply {
+ assertThat(roomTopic).isEqualTo("Another topic")
+ assertThat(roomName).isEqualTo("Name III")
+ assertThat(roomAvatarUrl).isNull()
+ }
+ }
+ }
+
+ @Test
+ fun `present - obtains avatar uris from gallery`() = runTest {
+ val room = aMatrixRoom(topic = "My topic", name = "Name", avatarUrl = AN_AVATAR_URL)
+
+ fakePickerProvider.givenResult(anotherAvatarUri)
+
+ val presenter = aRoomDetailsEditPresenter(room)
+
+ moleculeFlow(RecompositionClock.Immediate) {
+ presenter.present()
+ }.test {
+ val initialState = awaitItem()
+ assertThat(initialState.roomAvatarUrl).isEqualTo(roomAvatarUri)
+
+ initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto))
+ awaitItem().apply {
+ assertThat(roomAvatarUrl).isEqualTo(anotherAvatarUri)
+ }
+ }
+ }
+
+ @Test
+ fun `present - obtains avatar uris from camera`() = runTest {
+ val room = aMatrixRoom(topic = "My topic", name = "Name", avatarUrl = AN_AVATAR_URL)
+
+ fakePickerProvider.givenResult(anotherAvatarUri)
+
+ val presenter = aRoomDetailsEditPresenter(room)
+
+ moleculeFlow(RecompositionClock.Immediate) {
+ presenter.present()
+ }.test {
+ val initialState = awaitItem()
+ assertThat(initialState.roomAvatarUrl).isEqualTo(roomAvatarUri)
+
+ initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.TakePhoto))
+ awaitItem().apply {
+ assertThat(roomAvatarUrl).isEqualTo(anotherAvatarUri)
+ }
+ }
+ }
+
+ @Test
+ fun `present - updates save button state`() = runTest {
+ val room = aMatrixRoom(topic = "My topic", name = "Name", avatarUrl = AN_AVATAR_URL)
+
+ fakePickerProvider.givenResult(roomAvatarUri)
+
+ val presenter = aRoomDetailsEditPresenter(room)
+
+ moleculeFlow(RecompositionClock.Immediate) {
+ presenter.present()
+ }.test {
+ val initialState = awaitItem()
+ assertThat(initialState.saveButtonEnabled).isEqualTo(false)
+
+ // Once a change is made, the save button is enabled
+ initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName("Name II"))
+ awaitItem().apply {
+ assertThat(saveButtonEnabled).isEqualTo(true)
+ }
+
+ // If it's reverted then the save disables again
+ initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName("Name"))
+ awaitItem().apply {
+ assertThat(saveButtonEnabled).isEqualTo(false)
+ }
+
+ // Make a change...
+ initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic("Another topic"))
+ awaitItem().apply {
+ assertThat(saveButtonEnabled).isEqualTo(true)
+ }
+
+ // Revert it...
+ initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic("My topic"))
+ awaitItem().apply {
+ assertThat(saveButtonEnabled).isEqualTo(false)
+ }
+
+ // Make a change...
+ initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.Remove))
+ awaitItem().apply {
+ assertThat(saveButtonEnabled).isEqualTo(true)
+ }
+
+ // Revert it...
+ initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto))
+ awaitItem().apply {
+ assertThat(saveButtonEnabled).isEqualTo(false)
+ }
+ }
+ }
+
+ @Test
+ fun `present - updates save button state when initial values are null`() = runTest {
+ val room = aMatrixRoom(topic = null, name = null, displayName = "fallback", avatarUrl = null)
+
+ fakePickerProvider.givenResult(roomAvatarUri)
+
+ val presenter = aRoomDetailsEditPresenter(room)
+
+ moleculeFlow(RecompositionClock.Immediate) {
+ presenter.present()
+ }.test {
+ val initialState = awaitItem()
+ assertThat(initialState.saveButtonEnabled).isEqualTo(false)
+
+ // Once a change is made, the save button is enabled
+ initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName("Name II"))
+ awaitItem().apply {
+ assertThat(saveButtonEnabled).isEqualTo(true)
+ }
+
+ // If it's reverted then the save disables again
+ initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName("fallback"))
+ awaitItem().apply {
+ assertThat(saveButtonEnabled).isEqualTo(false)
+ }
+
+ // Make a change...
+ initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic("Another topic"))
+ awaitItem().apply {
+ assertThat(saveButtonEnabled).isEqualTo(true)
+ }
+
+ // Revert it...
+ initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic(""))
+ awaitItem().apply {
+ assertThat(saveButtonEnabled).isEqualTo(false)
+ }
+
+ // Make a change...
+ initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto))
+ awaitItem().apply {
+ assertThat(saveButtonEnabled).isEqualTo(true)
+ }
+
+ // Revert it...
+ initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.Remove))
+ awaitItem().apply {
+ assertThat(saveButtonEnabled).isEqualTo(false)
+ }
+ }
+ }
+
+ @Test
+ fun `present - save changes room details if different`() = runTest {
+ val room = aMatrixRoom(topic = "My topic", name = "Name", avatarUrl = AN_AVATAR_URL)
+
+ val presenter = aRoomDetailsEditPresenter(room)
+
+ moleculeFlow(RecompositionClock.Immediate) {
+ presenter.present()
+ }.test {
+ val initialState = awaitItem()
+
+ initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName("New name"))
+ initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic("New topic"))
+ initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.Remove))
+ initialState.eventSink(RoomDetailsEditEvents.Save)
+
+ assertThat(room.newName).isEqualTo("New name")
+ assertThat(room.newTopic).isEqualTo("New topic")
+ assertThat(room.newAvatarData).isNull()
+ assertThat(room.removedAvatar).isTrue()
+
+ cancelAndIgnoreRemainingEvents()
+ }
+ }
+
+ @Test
+ fun `present - save doesn't change room details if they're the same trimmed`() = runTest {
+ val room = aMatrixRoom(topic = "My topic", name = "Name", avatarUrl = AN_AVATAR_URL)
+
+ val presenter = aRoomDetailsEditPresenter(room)
+
+ moleculeFlow(RecompositionClock.Immediate) {
+ presenter.present()
+ }.test {
+ val initialState = awaitItem()
+
+ initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName(" Name "))
+ initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic(" My topic "))
+ initialState.eventSink(RoomDetailsEditEvents.Save)
+
+ assertThat(room.newName).isNull()
+ assertThat(room.newTopic).isNull()
+ assertThat(room.newAvatarData).isNull()
+ assertThat(room.removedAvatar).isFalse()
+
+ cancelAndIgnoreRemainingEvents()
+ }
+ }
+
+ @Test
+ fun `present - save doesn't change topic if it was unset and is now blank`() = runTest {
+ val room = aMatrixRoom(topic = null, name = "Name", avatarUrl = AN_AVATAR_URL)
+
+ val presenter = aRoomDetailsEditPresenter(room)
+
+ moleculeFlow(RecompositionClock.Immediate) {
+ presenter.present()
+ }.test {
+ val initialState = awaitItem()
+
+ initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic(""))
+ initialState.eventSink(RoomDetailsEditEvents.Save)
+
+ assertThat(room.newName).isNull()
+ assertThat(room.newTopic).isNull()
+ assertThat(room.newAvatarData).isNull()
+ assertThat(room.removedAvatar).isFalse()
+
+ cancelAndIgnoreRemainingEvents()
+ }
+ }
+
+ @Test
+ fun `present - save doesn't change name if it's now empty`() = runTest {
+ val room = aMatrixRoom(topic = "My topic", name = "Name", avatarUrl = AN_AVATAR_URL)
+
+ val presenter = aRoomDetailsEditPresenter(room)
+
+ moleculeFlow(RecompositionClock.Immediate) {
+ presenter.present()
+ }.test {
+ val initialState = awaitItem()
+
+ initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName(""))
+ initialState.eventSink(RoomDetailsEditEvents.Save)
+
+ assertThat(room.newName).isNull()
+ assertThat(room.newTopic).isNull()
+ assertThat(room.newAvatarData).isNull()
+ assertThat(room.removedAvatar).isFalse()
+
+ cancelAndIgnoreRemainingEvents()
+ }
+ }
+
+ @Test
+ fun `present - save processes and sets avatar when processor returns successfully`() = runTest {
+ val room = aMatrixRoom(topic = "My topic", name = "Name", avatarUrl = AN_AVATAR_URL)
+
+ givenPickerReturnsFile()
+
+ val presenter = aRoomDetailsEditPresenter(room)
+
+ moleculeFlow(RecompositionClock.Immediate) {
+ presenter.present()
+ }.test {
+ val initialState = awaitItem()
+
+ initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto))
+ initialState.eventSink(RoomDetailsEditEvents.Save)
+ skipItems(2)
+
+ assertThat(room.newName).isNull()
+ assertThat(room.newTopic).isNull()
+ assertThat(room.newAvatarData).isSameInstanceAs(fakeFileContents)
+ assertThat(room.removedAvatar).isFalse()
+ }
+ }
+
+ @Test
+ fun `present - save does not set avatar data if processor fails`() = runTest {
+ val room = aMatrixRoom(topic = "My topic", name = "Name", avatarUrl = AN_AVATAR_URL)
+
+ fakePickerProvider.givenResult(anotherAvatarUri)
+ fakeMediaPreProcessor.givenResult(Result.failure(Throwable("Oh no")))
+
+ val presenter = aRoomDetailsEditPresenter(room)
+
+ moleculeFlow(RecompositionClock.Immediate) {
+ presenter.present()
+ }.test {
+ val initialState = awaitItem()
+
+ initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto))
+ initialState.eventSink(RoomDetailsEditEvents.Save)
+ skipItems(1)
+
+ assertThat(room.newName).isNull()
+ assertThat(room.newTopic).isNull()
+ assertThat(room.newAvatarData).isNull()
+ assertThat(room.removedAvatar).isFalse()
+
+ assertThat(awaitItem().saveAction).isInstanceOf(Async.Failure::class.java)
+ }
+ }
+
+ @Test
+ fun `present - sets save action to failure if name update fails`() = runTest {
+ val room = aMatrixRoom(topic = "My topic", name = "Name", avatarUrl = AN_AVATAR_URL).apply {
+ givenSetNameResult(Result.failure(Throwable("!")))
+ }
+
+ saveAndAssertFailure(room, RoomDetailsEditEvents.UpdateRoomName("New name"))
+ }
+
+ @Test
+ fun `present - sets save action to failure if topic update fails`() = runTest {
+ val room = aMatrixRoom(topic = "My topic", name = "Name", avatarUrl = AN_AVATAR_URL).apply {
+ givenSetTopicResult(Result.failure(Throwable("!")))
+ }
+
+ saveAndAssertFailure(room, RoomDetailsEditEvents.UpdateRoomTopic("New topic"))
+ }
+
+ @Test
+ fun `present - sets save action to failure if removing avatar fails`() = runTest {
+ val room = aMatrixRoom(topic = "My topic", name = "Name", avatarUrl = AN_AVATAR_URL).apply {
+ givenRemoveAvatarResult(Result.failure(Throwable("!")))
+ }
+
+ saveAndAssertFailure(room, RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.Remove))
+ }
+
+ @Test
+ fun `present - sets save action to failure if setting avatar fails`() = runTest {
+ givenPickerReturnsFile()
+
+ val room = aMatrixRoom(topic = "My topic", name = "Name", avatarUrl = AN_AVATAR_URL).apply {
+ givenUpdateAvatarResult(Result.failure(Throwable("!")))
+ }
+
+ saveAndAssertFailure(room, RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto))
+ }
+
+ @Test
+ fun `present - CancelSaveChanges resets save action state`() = runTest {
+ givenPickerReturnsFile()
+
+ val room = aMatrixRoom(topic = "My topic", name = "Name", avatarUrl = AN_AVATAR_URL).apply {
+ givenSetTopicResult(Result.failure(Throwable("!")))
+ }
+
+ val presenter = aRoomDetailsEditPresenter(room)
+
+ moleculeFlow(RecompositionClock.Immediate) {
+ presenter.present()
+ }.test {
+ val initialState = awaitItem()
+
+ initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic("foo"))
+ initialState.eventSink(RoomDetailsEditEvents.Save)
+ skipItems(1)
+
+ assertThat(awaitItem().saveAction).isInstanceOf(Async.Failure::class.java)
+
+ initialState.eventSink(RoomDetailsEditEvents.CancelSaveChanges)
+ assertThat(awaitItem().saveAction).isInstanceOf(Async.Uninitialized::class.java)
+ }
+ }
+
+ private suspend fun saveAndAssertFailure(room: MatrixRoom, event: RoomDetailsEditEvents) {
+ val presenter = aRoomDetailsEditPresenter(room)
+
+ moleculeFlow(RecompositionClock.Immediate) {
+ presenter.present()
+ }.test {
+ val initialState = awaitItem()
+
+ initialState.eventSink(event)
+ initialState.eventSink(RoomDetailsEditEvents.Save)
+ skipItems(1)
+
+ assertThat(awaitItem().saveAction).isInstanceOf(Async.Failure::class.java)
+ }
+ }
+
+ private fun givenPickerReturnsFile() {
+ mockkStatic(File::readBytes)
+ val processedFile: File = mockk {
+ every { readBytes() } returns fakeFileContents
+ }
+
+ fakePickerProvider.givenResult(anotherAvatarUri)
+ fakeMediaPreProcessor.givenResult(Result.success(MediaUploadInfo.Image(
+ file = processedFile,
+ info = mockk(),
+ thumbnailInfo = ThumbnailProcessingInfo(
+ file = processedFile,
+ info = mockk(),
+ blurhash = "",
+ )
+ )))
+ }
+
+ companion object {
+ private const val ANOTHER_AVATAR_URL = "example://camera/foo.jpg"
+ }
+
+}
diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/Color.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/Color.kt
index 50585f9dba..e61b2e66a3 100644
--- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/Color.kt
+++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/Color.kt
@@ -79,3 +79,6 @@ val Compound_Gray_300_Light = Color(0xFFF0F2F5)
val Compound_Gray_300_Dark = Color(0xFF1D1F24)
val Compound_Gray_400_Light = Color(0xFFE1E6EC)
val Compound_Gray_400_Dark = Color(0xFF26282D)
+
+val Gray_1400_Light = Color(0xFF1B1D22)
+val Gray_1400_Dark = Color(0xFFEBEEF2)
diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/LabelledTextField.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/LabelledTextField.kt
similarity index 77%
rename from features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/LabelledTextField.kt
rename to libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/LabelledTextField.kt
index 2a28c56253..0f0ada5497 100644
--- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/LabelledTextField.kt
+++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/LabelledTextField.kt
@@ -14,18 +14,17 @@
* limitations under the License.
*/
-package io.element.android.features.createroom.impl.components
+package io.element.android.libraries.designsystem.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import io.element.android.features.createroom.impl.R
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Text
@@ -36,8 +35,9 @@ fun LabelledTextField(
label: String,
value: String,
modifier: Modifier = Modifier,
- placeholder: String = "",
- maxLines: Int = 1,
+ placeholder: String? = null,
+ maxLines: Int = Int.MAX_VALUE,
+ singleLine: Boolean = false,
onValueChange: (String) -> Unit = {},
) {
Column(
@@ -46,15 +46,17 @@ fun LabelledTextField(
) {
Text(
modifier = Modifier.padding(horizontal = 16.dp),
+ style = MaterialTheme.typography.titleSmall,
+ color = MaterialTheme.colorScheme.primary,
text = label
)
TextField(
modifier = Modifier.fillMaxWidth(),
value = value,
- placeholder = { Text(placeholder) },
+ placeholder = placeholder?.let { { Text(placeholder) } },
onValueChange = onValueChange,
- singleLine = maxLines == 1,
+ singleLine = singleLine,
maxLines = maxLines,
)
}
@@ -72,14 +74,14 @@ fun LabelledTextFieldDarkPreview() = ElementPreviewDark { ContentToPreview() }
private fun ContentToPreview() {
Column {
LabelledTextField(
- label = stringResource(R.string.screen_create_room_room_name_label),
+ label = "Room name",
value = "",
- placeholder = stringResource(R.string.screen_create_room_room_name_placeholder),
+ placeholder = "e.g. Product Sprint",
)
LabelledTextField(
- label = stringResource(R.string.screen_create_room_room_name_label),
+ label = "Room name",
value = "a room name",
- placeholder = stringResource(R.string.screen_create_room_room_name_placeholder),
+ placeholder = "e.g. Product Sprint",
)
}
}
diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorsDark.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorsDark.kt
index 166a2f3f79..56a287a9e9 100644
--- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorsDark.kt
+++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorsDark.kt
@@ -25,6 +25,7 @@ import io.element.android.libraries.designsystem.Black_800
import io.element.android.libraries.designsystem.Black_950
import io.element.android.libraries.designsystem.Compound_Gray_300_Dark
import io.element.android.libraries.designsystem.DarkGrey
+import io.element.android.libraries.designsystem.Gray_1400_Dark
import io.element.android.libraries.designsystem.Gray_300
import io.element.android.libraries.designsystem.Gray_400
import io.element.android.libraries.designsystem.Compound_Gray_400_Dark
@@ -42,6 +43,7 @@ fun elementColorsDark() = ElementColors(
quinary = Gray_450,
gray300 = Compound_Gray_300_Dark,
gray400 = Compound_Gray_400_Dark,
+ gray1400 = Gray_1400_Dark,
textActionCritical = TextColorCriticalDark,
isLight = false,
)
diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorsLight.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorsLight.kt
index 085dd534cd..696baca384 100644
--- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorsLight.kt
+++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorsLight.kt
@@ -22,12 +22,13 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.Azure
import io.element.android.libraries.designsystem.Black_900
+import io.element.android.libraries.designsystem.Compound_Gray_300_Light
+import io.element.android.libraries.designsystem.Compound_Gray_400_Light
import io.element.android.libraries.designsystem.Gray_100
+import io.element.android.libraries.designsystem.Gray_1400_Light
import io.element.android.libraries.designsystem.Gray_150
import io.element.android.libraries.designsystem.Gray_200
import io.element.android.libraries.designsystem.Gray_25
-import io.element.android.libraries.designsystem.Compound_Gray_300_Light
-import io.element.android.libraries.designsystem.Compound_Gray_400_Light
import io.element.android.libraries.designsystem.Gray_50
import io.element.android.libraries.designsystem.SystemGrey5Light
import io.element.android.libraries.designsystem.SystemGrey6Light
@@ -42,6 +43,7 @@ fun elementColorsLight() = ElementColors(
quinary = Gray_50,
gray300 = Compound_Gray_300_Light,
gray400 = Compound_Gray_400_Light,
+ gray1400 = Gray_1400_Light,
textActionCritical = TextColorCriticalLight,
isLight = true,
)
diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementColors.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementColors.kt
index 2643e678d3..9f7139b446 100644
--- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementColors.kt
+++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementColors.kt
@@ -31,6 +31,7 @@ class ElementColors(
quinary: Color,
gray300: Color,
gray400: Color,
+ gray1400: Color,
textActionCritical: Color,
isLight: Boolean
) {
@@ -53,6 +54,9 @@ class ElementColors(
var gray400 by mutableStateOf(gray400)
private set
+ var gray1400 by mutableStateOf(gray1400)
+ private set
+
var textActionCritical by mutableStateOf(textActionCritical)
private set
@@ -67,6 +71,7 @@ class ElementColors(
quinary: Color = this.quinary,
gray300: Color = this.gray300,
gray400: Color = this.gray400,
+ gray1400: Color = this.gray1400,
textActionCritical: Color = this.textActionCritical,
isLight: Boolean = this.isLight,
) = ElementColors(
@@ -77,6 +82,7 @@ class ElementColors(
quinary = quinary,
gray300 = gray300,
gray400 = gray400,
+ gray1400 = gray1400,
textActionCritical = textActionCritical,
isLight = isLight,
)
@@ -89,6 +95,7 @@ class ElementColors(
quinary = other.quinary
gray300 = other.gray300
gray400 = other.gray400
+ gray1400 = other.gray1400
textActionCritical = other.textActionCritical
isLight = other.isLight
}
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt
index 5cf9375e0a..edf85cf471 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt
@@ -89,4 +89,14 @@ interface MatrixRoom : Closeable {
suspend fun inviteUserById(id: UserId): Result
suspend fun canInvite(): Result
+
+ suspend fun canSendStateEvent(type: StateEventType): Result
+
+ suspend fun updateAvatar(mimeType: String, data: ByteArray): Result
+
+ suspend fun removeAvatar(): Result
+
+ suspend fun setName(name: String): Result
+
+ suspend fun setTopic(topic: String): Result
}
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StateEventType.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StateEventType.kt
new file mode 100644
index 0000000000..50cde59b37
--- /dev/null
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StateEventType.kt
@@ -0,0 +1,41 @@
+/*
+ * 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 StateEventType {
+ POLICY_RULE_ROOM,
+ POLICY_RULE_SERVER,
+ POLICY_RULE_USER,
+ ROOM_ALIASES,
+ ROOM_AVATAR,
+ ROOM_CANONICAL_ALIAS,
+ ROOM_CREATE,
+ ROOM_ENCRYPTION,
+ ROOM_GUEST_ACCESS,
+ ROOM_HISTORY_VISIBILITY,
+ ROOM_JOIN_RULES,
+ ROOM_MEMBER_EVENT,
+ ROOM_NAME,
+ ROOM_PINNED_EVENTS,
+ ROOM_POWER_LEVELS,
+ ROOM_SERVER_ACL,
+ ROOM_THIRD_PARTY_INVITE,
+ ROOM_TOMBSTONE,
+ ROOM_TOPIC,
+ SPACE_CHILD,
+ SPACE_PARENT;
+}
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt
index c21ceb5076..383710873a 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt
@@ -27,12 +27,14 @@ import io.element.android.libraries.matrix.api.media.ImageInfo
import io.element.android.libraries.matrix.api.media.VideoInfo
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
+import io.element.android.libraries.matrix.api.room.StateEventType
import io.element.android.libraries.matrix.api.room.roomMembers
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.impl.media.map
import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline
import io.element.android.services.toolbox.api.systemclock.SystemClock
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -224,6 +226,12 @@ class RustMatrixRoom(
}
}
+ override suspend fun canSendStateEvent(type: StateEventType): Result = withContext(coroutineDispatchers.io) {
+ runCatching {
+ innerRoom.member(sessionId.value).use { it.canSendState(type.map()) }
+ }
+ }
+
override suspend fun sendImage(file: File, thumbnailFile: File, imageInfo: ImageInfo): Result = withContext(coroutineDispatchers.io) {
runCatching {
innerRoom.sendImage(file.path, thumbnailFile.path, imageInfo.map())
@@ -247,4 +255,33 @@ class RustMatrixRoom(
innerRoom.sendFile(file.path, fileInfo.map())
}
}
+
+ @OptIn(ExperimentalUnsignedTypes::class)
+ override suspend fun updateAvatar(mimeType: String, data: ByteArray): Result =
+ withContext(Dispatchers.IO) {
+ runCatching {
+ innerRoom.uploadAvatar(mimeType, data.toUByteArray().toList())
+ }
+ }
+
+ override suspend fun removeAvatar(): Result =
+ withContext(Dispatchers.IO) {
+ runCatching {
+ innerRoom.removeAvatar()
+ }
+ }
+
+ override suspend fun setName(name: String): Result =
+ withContext(Dispatchers.IO) {
+ runCatching {
+ innerRoom.setName(name)
+ }
+ }
+
+ override suspend fun setTopic(topic: String): Result =
+ withContext(Dispatchers.IO) {
+ runCatching {
+ innerRoom.setTopic(topic)
+ }
+ }
}
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/StateEventType.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/StateEventType.kt
new file mode 100644
index 0000000000..2cd09e213c
--- /dev/null
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/StateEventType.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.StateEventType
+import org.matrix.rustcomponents.sdk.StateEventType as RustStateEventType
+
+fun StateEventType.map(): RustStateEventType = when (this) {
+ StateEventType.POLICY_RULE_ROOM -> RustStateEventType.POLICY_RULE_ROOM
+ StateEventType.POLICY_RULE_SERVER -> RustStateEventType.POLICY_RULE_SERVER
+ StateEventType.POLICY_RULE_USER -> RustStateEventType.POLICY_RULE_USER
+ StateEventType.ROOM_ALIASES -> RustStateEventType.ROOM_ALIASES
+ StateEventType.ROOM_AVATAR -> RustStateEventType.ROOM_AVATAR
+ StateEventType.ROOM_CANONICAL_ALIAS -> RustStateEventType.ROOM_CANONICAL_ALIAS
+ StateEventType.ROOM_CREATE -> RustStateEventType.ROOM_CREATE
+ StateEventType.ROOM_ENCRYPTION -> RustStateEventType.ROOM_ENCRYPTION
+ StateEventType.ROOM_GUEST_ACCESS -> RustStateEventType.ROOM_GUEST_ACCESS
+ StateEventType.ROOM_HISTORY_VISIBILITY -> RustStateEventType.ROOM_HISTORY_VISIBILITY
+ StateEventType.ROOM_JOIN_RULES -> RustStateEventType.ROOM_JOIN_RULES
+ StateEventType.ROOM_MEMBER_EVENT -> RustStateEventType.ROOM_MEMBER_EVENT
+ StateEventType.ROOM_NAME -> RustStateEventType.ROOM_NAME
+ StateEventType.ROOM_PINNED_EVENTS -> RustStateEventType.ROOM_PINNED_EVENTS
+ StateEventType.ROOM_POWER_LEVELS -> RustStateEventType.ROOM_POWER_LEVELS
+ StateEventType.ROOM_SERVER_ACL -> RustStateEventType.ROOM_SERVER_ACL
+ StateEventType.ROOM_THIRD_PARTY_INVITE -> RustStateEventType.ROOM_THIRD_PARTY_INVITE
+ StateEventType.ROOM_TOMBSTONE -> RustStateEventType.ROOM_TOMBSTONE
+ StateEventType.ROOM_TOPIC -> RustStateEventType.ROOM_TOPIC
+ StateEventType.SPACE_CHILD -> RustStateEventType.SPACE_CHILD
+ StateEventType.SPACE_PARENT -> RustStateEventType.SPACE_PARENT
+}
+
+fun RustStateEventType.map(): StateEventType = when (this) {
+ RustStateEventType.POLICY_RULE_ROOM -> StateEventType.POLICY_RULE_ROOM
+ RustStateEventType.POLICY_RULE_SERVER -> StateEventType.POLICY_RULE_SERVER
+ RustStateEventType.POLICY_RULE_USER -> StateEventType.POLICY_RULE_USER
+ RustStateEventType.ROOM_ALIASES -> StateEventType.ROOM_ALIASES
+ RustStateEventType.ROOM_AVATAR -> StateEventType.ROOM_AVATAR
+ RustStateEventType.ROOM_CANONICAL_ALIAS -> StateEventType.ROOM_CANONICAL_ALIAS
+ RustStateEventType.ROOM_CREATE -> StateEventType.ROOM_CREATE
+ RustStateEventType.ROOM_ENCRYPTION -> StateEventType.ROOM_ENCRYPTION
+ RustStateEventType.ROOM_GUEST_ACCESS -> StateEventType.ROOM_GUEST_ACCESS
+ RustStateEventType.ROOM_HISTORY_VISIBILITY -> StateEventType.ROOM_HISTORY_VISIBILITY
+ RustStateEventType.ROOM_JOIN_RULES -> StateEventType.ROOM_JOIN_RULES
+ RustStateEventType.ROOM_MEMBER_EVENT -> StateEventType.ROOM_MEMBER_EVENT
+ RustStateEventType.ROOM_NAME -> StateEventType.ROOM_NAME
+ RustStateEventType.ROOM_PINNED_EVENTS -> StateEventType.ROOM_PINNED_EVENTS
+ RustStateEventType.ROOM_POWER_LEVELS -> StateEventType.ROOM_POWER_LEVELS
+ RustStateEventType.ROOM_SERVER_ACL -> StateEventType.ROOM_SERVER_ACL
+ RustStateEventType.ROOM_THIRD_PARTY_INVITE -> StateEventType.ROOM_THIRD_PARTY_INVITE
+ RustStateEventType.ROOM_TOMBSTONE -> StateEventType.ROOM_TOMBSTONE
+ RustStateEventType.ROOM_TOPIC -> StateEventType.ROOM_TOPIC
+ RustStateEventType.SPACE_CHILD -> StateEventType.SPACE_CHILD
+ RustStateEventType.SPACE_PARENT -> StateEventType.SPACE_PARENT
+}
diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt
index ff91db14b3..8769d9ddfb 100644
--- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt
+++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt
@@ -26,6 +26,7 @@ import io.element.android.libraries.matrix.api.media.ImageInfo
import io.element.android.libraries.matrix.api.media.VideoInfo
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
+import io.element.android.libraries.matrix.api.room.StateEventType
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_SESSION_ID
@@ -62,7 +63,13 @@ class FakeMatrixRoom(
private var rejectInviteResult = Result.success(Unit)
private var inviteUserResult = Result.success(Unit)
private var canInviteResult = Result.success(true)
+ private val canSendStateResults = mutableMapOf>()
private var sendMediaResult = Result.success(Unit)
+ private var setNameResult = Result.success(Unit)
+ private var setTopicResult = Result.success(Unit)
+ private var updateAvatarResult = Result.success(Unit)
+ private var removeAvatarResult = Result.success(Unit)
+
var sendMediaCount = 0
private set
@@ -75,6 +82,18 @@ class FakeMatrixRoom(
var invitedUserId: UserId? = null
private set
+ var newTopic: String? = null
+ private set
+
+ var newName: String? = null
+ private set
+
+ var newAvatarData: ByteArray? = null
+ private set
+
+ var removedAvatar: Boolean = false
+ private set
+
private var leaveRoomError: Throwable? = null
override val membersStateFlow: MutableStateFlow = MutableStateFlow(MatrixRoomMembersState.Unknown)
@@ -151,6 +170,10 @@ class FakeMatrixRoom(
return canInviteResult
}
+ override suspend fun canSendStateEvent(type: StateEventType): Result {
+ return canSendStateResults[type] ?: Result.failure(IllegalStateException("No fake answer"))
+ }
+
override suspend fun sendImage(file: File, thumbnailFile: File, imageInfo: ImageInfo): Result = fakeSendMedia()
override suspend fun sendVideo(file: File, thumbnailFile: File, videoInfo: VideoInfo): Result = fakeSendMedia()
@@ -166,6 +189,26 @@ class FakeMatrixRoom(
}
}
+ override suspend fun updateAvatar(mimeType: String, data: ByteArray): Result {
+ newAvatarData = data
+ return updateAvatarResult
+ }
+
+ override suspend fun removeAvatar(): Result {
+ removedAvatar = true
+ return removeAvatarResult
+ }
+
+ override suspend fun setName(name: String): Result {
+ newName = name
+ return setNameResult
+ }
+
+ override suspend fun setTopic(topic: String): Result {
+ newTopic = topic
+ return setTopicResult
+ }
+
override fun close() = Unit
fun givenLeaveRoomError(throwable: Throwable?) {
@@ -204,6 +247,10 @@ class FakeMatrixRoom(
canInviteResult = result
}
+ fun givenCanSendStateResult(type: StateEventType, result: Result) {
+ canSendStateResults[type] = result
+ }
+
fun givenIgnoreResult(result: Result) {
ignoreResult = result
}
@@ -215,4 +262,20 @@ class FakeMatrixRoom(
fun givenSendMediaResult(result: Result) {
sendMediaResult = result
}
+
+ fun givenUpdateAvatarResult(result: Result) {
+ updateAvatarResult = result
+ }
+
+ fun givenRemoveAvatarResult(result: Result) {
+ removeAvatarResult = result
+ }
+
+ fun givenSetNameResult(result: Result) {
+ setNameResult = result
+ }
+
+ fun givenSetTopicResult(result: Result) {
+ setTopicResult = result
+ }
}
diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/avatar/AvatarActionListView.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AvatarActionBottomSheet.kt
similarity index 92%
rename from features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/avatar/AvatarActionListView.kt
rename to libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AvatarActionBottomSheet.kt
index 422a187290..59178d3a45 100644
--- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/avatar/AvatarActionListView.kt
+++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AvatarActionBottomSheet.kt
@@ -16,7 +16,7 @@
@file:OptIn(ExperimentalMaterialApi::class)
-package io.element.android.features.createroom.impl.configureroom.avatar
+package io.element.android.libraries.matrix.ui.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.fillMaxWidth
@@ -39,12 +39,13 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.ModalBottomSheetLayout
+import io.element.android.libraries.matrix.ui.media.AvatarAction
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.launch
@Composable
-fun AvatarActionListView(
+fun AvatarActionBottomSheet(
actions: ImmutableList,
modalBottomSheetState: ModalBottomSheetState,
modifier: Modifier = Modifier,
@@ -62,7 +63,7 @@ fun AvatarActionListView(
modifier = modifier,
sheetState = modalBottomSheetState,
sheetContent = {
- SheetContent(
+ AvatarActionBottomSheetContent(
actions = actions,
onActionClicked = ::onItemActionClicked,
modifier = Modifier
@@ -74,7 +75,7 @@ fun AvatarActionListView(
}
@Composable
-private fun SheetContent(
+private fun AvatarActionBottomSheetContent(
actions: ImmutableList,
modifier: Modifier = Modifier,
onActionClicked: (AvatarAction) -> Unit = { },
@@ -107,17 +108,17 @@ private fun SheetContent(
@Preview
@Composable
-fun SheetContentLightPreview() =
+fun AvatarActionBottomSheetLightPreview() =
ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
-fun SheetContentDarkPreview() =
+fun AvatarActionBottomSheetDarkPreview() =
ElementPreviewDark { ContentToPreview() }
@Composable
private fun ContentToPreview() {
- AvatarActionListView(
+ AvatarActionBottomSheet(
actions = persistentListOf(AvatarAction.TakePhoto, AvatarAction.ChoosePhoto, AvatarAction.Remove),
modalBottomSheetState = ModalBottomSheetState(
initialValue = ModalBottomSheetValue.Expanded
diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/Avatar.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UnsavedAvatar.kt
similarity index 86%
rename from features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/Avatar.kt
rename to libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UnsavedAvatar.kt
index bbaf5c46e5..e5816ac014 100644
--- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/Avatar.kt
+++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UnsavedAvatar.kt
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-package io.element.android.features.createroom.impl.components
+package io.element.android.libraries.matrix.ui.components
import android.net.Uri
import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
@@ -43,16 +42,19 @@ import io.element.android.libraries.designsystem.preview.debugPlaceholderBackgro
import io.element.android.libraries.designsystem.theme.LocalColors
import io.element.android.libraries.designsystem.theme.components.Icon
+/**
+ * An avatar that the user has selected, but which has not yet been uploaded to Matrix.
+ *
+ * The image is loaded from a local resource instead of from a MXC URI.
+ */
@Composable
-fun Avatar(
+fun UnsavedAvatar(
avatarUri: Uri?,
modifier: Modifier = Modifier,
- onClick: () -> Unit = {},
) {
val commonModifier = modifier
.size(70.dp)
.clip(CircleShape)
- .clickable(onClick = onClick)
if (avatarUri != null) {
val context = LocalContext.current
@@ -82,16 +84,16 @@ fun Avatar(
@Preview
@Composable
-fun AvatarLightPreview() = ElementPreviewLight { ContentToPreview() }
+fun UnsavedAvatarLightPreview() = ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
-fun AvatarDarkPreview() = ElementPreviewDark { ContentToPreview() }
+fun UnsavedAvatarDarkPreview() = ElementPreviewDark { ContentToPreview() }
@Composable
private fun ContentToPreview() {
Row {
- Avatar(null)
- Avatar(Uri.EMPTY)
+ UnsavedAvatar(null)
+ UnsavedAvatar(Uri.EMPTY)
}
}
diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/avatar/AvatarAction.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarAction.kt
similarity index 95%
rename from features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/avatar/AvatarAction.kt
rename to libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarAction.kt
index 25ecc2b3db..624eb7c607 100644
--- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/avatar/AvatarAction.kt
+++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarAction.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package io.element.android.features.createroom.impl.configureroom.avatar
+package io.element.android.libraries.matrix.ui.media
import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.addpeople_null_DefaultGroup_AddPeopleViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.addpeople_null_DefaultGroup_AddPeopleViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png
index 525854695a..5c4f9b2afa 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.addpeople_null_DefaultGroup_AddPeopleViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.addpeople_null_DefaultGroup_AddPeopleViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:55d774d5e61f8832162859ec9d22299efa5b0728cfc3308b13fb11df31c5130e
-size 13790
+oid sha256:20e9edff7567936627718427349a2ca7bf318a909b6ca206304deab0149a796d
+size 13934
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.addpeople_null_DefaultGroup_AddPeopleViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.addpeople_null_DefaultGroup_AddPeopleViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png
index 596f01f6a6..274045391e 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.addpeople_null_DefaultGroup_AddPeopleViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.addpeople_null_DefaultGroup_AddPeopleViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:b96cf938377ee0f02e0cf2d5896eb83935d9416a5b87849ed81caed4db5e90f2
-size 41264
+oid sha256:ab9f1e15c5a071be9c2f63aa28e6fbd57c360960899f808cead275ef67a54ae6
+size 41444
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.addpeople_null_DefaultGroup_AddPeopleViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.addpeople_null_DefaultGroup_AddPeopleViewLightPreview_0_null_0,NEXUS_5,1.0,en].png
index 8ea3902e3a..419dd3269a 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.addpeople_null_DefaultGroup_AddPeopleViewLightPreview_0_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.addpeople_null_DefaultGroup_AddPeopleViewLightPreview_0_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:2ed229820d7cbd6b550e98116ec3bf688c52957e2a57ab296bfc5abd212345b1
-size 13784
+oid sha256:0b2350c880e73ce285bfeafafc851c6973e0372330ab6f4daa16d078ed6a81c9
+size 13884
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.addpeople_null_DefaultGroup_AddPeopleViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.addpeople_null_DefaultGroup_AddPeopleViewLightPreview_0_null_1,NEXUS_5,1.0,en].png
index 339b2adf16..85e99f70b1 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.addpeople_null_DefaultGroup_AddPeopleViewLightPreview_0_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.addpeople_null_DefaultGroup_AddPeopleViewLightPreview_0_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:92fe30f0927b8be4ffc384548e6731e7d5e347dc5caa1c84251f2a932ee1873c
-size 38848
+oid sha256:71d10fb1a2102466ab759442a1e006a3f5f85cb80f9f885cb5d18c8ded570bef
+size 38945
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.components_null_DefaultGroup_LabelledTextFieldDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.components_null_DefaultGroup_LabelledTextFieldDarkPreview_0_null,NEXUS_5,1.0,en].png
deleted file mode 100644
index 508f754a17..0000000000
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.components_null_DefaultGroup_LabelledTextFieldDarkPreview_0_null,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:fb3c154cf9fac2bc17b0ac3575bd2ce7745781cc1f2b9f28c220f98fb359c74d
-size 16284
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.components_null_DefaultGroup_LabelledTextFieldLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.components_null_DefaultGroup_LabelledTextFieldLightPreview_0_null,NEXUS_5,1.0,en].png
deleted file mode 100644
index 4bd0d46417..0000000000
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.components_null_DefaultGroup_LabelledTextFieldLightPreview_0_null,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:1d932221affde9bf53b06ea0e2b9c4b45dffe6adad7ff92f75775d9fe1d15b77
-size 16024
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png
index 734464a3ef..a12b50f256 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:a9da3afa82783ded1e686a36eb969b8e097de853e9c6dadea24884f7975c0720
-size 63729
+oid sha256:533ccd5b4e27b98ae4155e70d8cdde8a3c263ca613cc3e0ce31c2fc1d52758b8
+size 63361
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png
index dc5306778e..24c6168c3a 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:e2c200874d6fa8f4fe07da69a7879eb264fb8941c1ab9654162cb76586c52936
-size 103326
+oid sha256:7a036b609372a8c2f20307ca9f13b619973da8da45e81a019af1fdafadda64de
+size 102275
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewLightPreview_0_null_0,NEXUS_5,1.0,en].png
index 3983e46caf..2a7e793b29 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewLightPreview_0_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewLightPreview_0_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:e48bb979a1d328871157847862723b8342cd8c2704750045bbf24592e143a5e0
-size 57874
+oid sha256:4032529e222fb9d14d4dc1fd215c8aae5d63326809df7cec449011d7904a60d2
+size 58355
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewLightPreview_0_null_1,NEXUS_5,1.0,en].png
index bcd8ebf134..8a33e96fa4 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewLightPreview_0_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewLightPreview_0_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:4320fd1900a120a737ba2a56e6c1f55cf671eb0e8c1791dc605994d333feb0c2
-size 96888
+oid sha256:2bf49968f5b07ca6798c4c8670a9e173ef3a511c6c60ed8d55d652bb042b8a8b
+size 96370
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png
index de4377ecc9..4c9bab34c8 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:6f36c2b4f4266048d5295df08f380e4128630d11cce7ac11cf3a0eaaa5594d61
-size 19924
+oid sha256:4fddc08d41da556719424efbc75a1a80a57202095ce082124bc0bca08e54c74b
+size 21415
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewLightPreview_0_null_0,NEXUS_5,1.0,en].png
index 0bdbed7968..11d198b819 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewLightPreview_0_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewLightPreview_0_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:23b9e996b8f0cc2efcb3adb6294bfa7b4a53fefbb7b6ee07add4105da9b9d40e
-size 18984
+oid sha256:2639e6662a1c78ff0f6091fb72b5fc06b486aa08e3beed78e4877b9282ce98d6
+size 20309
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..9b2f7eb04e
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ec2051ec47f43a88aeb0d56078094fca784c948ce2b3e1a450343943ab7ee951
+size 35408
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..487980822c
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:85d54c4c049c16435c4e6bcc8bf6d25dbc3fccc90a0278c2c330c97334eb27d0
+size 28119
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..8209d5b40a
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6caa0bdf8897ab6d8d73b8dfeac5c8c3cece5945e87123a1f5a1c96de477e623
+size 32262
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..16e677ee95
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:df9d8b28c6e98632204e90bccfddcf1e0d0ca6ce1a19af03f5bc491ade32fa36
+size 34793
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..c6c6003d29
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bfed133c28a48fb90aeda89de0154b4ab5c209d08a75147065dd8cc126f955eb
+size 34416
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..d1bbb6763c
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d3e1f45de5ab8d0bd953bd2a0a0014b7dce3315a82d993ceb633304f2b8c2622
+size 28517
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewDarkPreview_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewDarkPreview_0_null_6,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..0c632d8d45
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewDarkPreview_0_null_6,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:02d7335bab9fd2bd753086ceeed886e57e0800b4d36a2cacd136c37bd41ebb9d
+size 27152
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewLightPreview_0_null_0,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..a1307b1cf9
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewLightPreview_0_null_0,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c61f9803c3313f6e2cceb966942bec7885fcb2efd87e94babdac8e37b95d4da5
+size 35380
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewLightPreview_0_null_1,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..7422209943
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewLightPreview_0_null_1,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a1a6d6b7fb6f2d97d63773dae499ed096dbf38d16cd34cafcee3b46571717bcb
+size 27828
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewLightPreview_0_null_2,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..ee9c9329b8
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewLightPreview_0_null_2,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:31d763b42ffc618bbf1c3272cbd4484edd7ab687f0ba25391955edf8f57c3208
+size 32443
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewLightPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewLightPreview_0_null_3,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..077fc03e17
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewLightPreview_0_null_3,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ca9b7a692c9d5bfa0ab0789f1d33a2f66c6244b1f4b672fab1aa18a71b3c9d0a
+size 33897
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewLightPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewLightPreview_0_null_4,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..cda549b019
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewLightPreview_0_null_4,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bdbeb867e5e009206a55eb1dfb0c478499301baa317a979cb2ffe9b1198871e8
+size 33931
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewLightPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewLightPreview_0_null_5,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..e866646102
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewLightPreview_0_null_5,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:dcf0472ccdfd1256eca966c6d29b4aace86f9ddab37164bb865633349a4377f3
+size 28519
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewLightPreview_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewLightPreview_0_null_6,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..d3cccbf61e
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.edit_null_DefaultGroup_RoomDetailsEditViewLightPreview_0_null_6,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3733e12a63cd72d529f9359f555cda4da65384bd7631f8450451b27095691193
+size 26945
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_0,NEXUS_5,1.0,en].png
index ee9f0fb113..38aaa95764 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:516e972069292f31625847ca39de06ea402ce1606c97d84f930c18eaa2448cfa
-size 13759
+oid sha256:9ba1f70268339a290e35e2127d1a3fa03840e69dc143631ef2fe29f22ddf646a
+size 15363
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_1,NEXUS_5,1.0,en].png
index a1094a6fa6..e40d7e6d34 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:b387f10903926ba9ed30b7d213314388eebf23743cbbdbcf4b613cf3136f64aa
-size 41524
+oid sha256:01b7d6a33e3591a54bcf602bac34155b85056ebdf600c511858efba55ea43cf4
+size 43034
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_2,NEXUS_5,1.0,en].png
index 79b48dd535..7c469f37db 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:0af8c7149fd4266c68c0e4b8b9a699bc0b3fc0b05fea3f2e2544abc884f70c5b
-size 11988
+oid sha256:cdb5e42bc94ca908c76b5da491ed712169876e8f2ea80534f2e722b7280ba72d
+size 13602
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_3,NEXUS_5,1.0,en].png
index ff95c9ed19..9648a14380 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:8eb2fcdb57b7f1ba28990f9576fad6dce156fe0c5c9ae491797040fc22ae4f7b
-size 39516
+oid sha256:656cec478d23350ed5c6e08b6cd9579e497999d6a7a8aa879af7dd223fefa98b
+size 41088
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_4,NEXUS_5,1.0,en].png
index f7f16c2b8a..3f7583050a 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_4,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_4,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:1e4d3bc53e6a86957218247b614be6bbc78cfb35ce49565f2243f50d6ef94fe3
-size 14216
+oid sha256:456b9c09e0ff314543fa6a0e54d7e6e41f346e3fd2ff33f9ba1ea3ae3168d629
+size 15839
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_5,NEXUS_5,1.0,en].png
index 0cd4f9c3e0..a604fa2859 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_5,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_5,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:213777e4ace6f9c33209db37bffff2ca6dba13e540e9d07be82239d261ee8e3f
-size 63951
+oid sha256:d76042bbb94307f96dc99d5077b58204b8f30a195408739271321570c218d0da
+size 65441
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_6,NEXUS_5,1.0,en].png
index 84abdcb0e5..7041e4be75 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_6,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersDarkPreview_0_null_6,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:cb1178f063eafb21ba01b93310d84335afbc4e6ad1fc99d1776578fe3f7eaa07
-size 49237
+oid sha256:22bda941040d3ad60d709517f07a27933ae25a5a69464e492fe1d0f9c4d7c405
+size 50488
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_0,NEXUS_5,1.0,en].png
index 57c7799447..29dd68cf13 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:8ddec11fd636d345c49cf981ce7498ac2d94027d4b223b257b64288bb0254766
-size 13328
+oid sha256:bcc75c47150e577714221b5d574a67c69051cf2f4439f8b7c929620509d87562
+size 14668
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_1,NEXUS_5,1.0,en].png
index 1db9b42586..7a5b1f7a23 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:da5ba4637d999c8654e38a86ad0b26c75a6fe10a6988eb821b24cf91da2274e5
-size 39070
+oid sha256:1df29f2bd4090162dcbf7336971c366b53b49390f5f41cc6df6c6f506f01ab30
+size 40464
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_2,NEXUS_5,1.0,en].png
index 6648389b0e..79171ae80b 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c57c20fe79d0dbb0ca25571fa2ab3ba6e06f3a9c6634e96fac3ed0155c2ea106
-size 11160
+oid sha256:d488d43188f491cc578d30a70d05c90d6c436ca8fad88a36b85a1ef5bc90e88b
+size 12564
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_3,NEXUS_5,1.0,en].png
index 5b6a14ea66..29762f9bf5 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:8e43279132a7536516cf267599f877f7c729f76be51d6319944f74c57af0e282
-size 36493
+oid sha256:900065642b4eb9a90a3a5d1bbbb5f03e9057b08e5e8289e535fd9237ac15e497
+size 37824
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_4,NEXUS_5,1.0,en].png
index fd948f750d..0a329a78cd 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_4,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_4,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:933c5ac4478699e9e713af559025ab675a8bdc93b15c42ff27fe5525fa3b668a
-size 13166
+oid sha256:b457fe4bc8ff8a26612819abbbac6440b539e61a8f00fb4171ad38dfb131840a
+size 14553
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_5,NEXUS_5,1.0,en].png
index a35148eff1..168be9c78c 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_5,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_5,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:3a66a39bf18ba35abfc87dea22ca019eea98727bf246142cb034649c5b1772bd
-size 61041
+oid sha256:8384acb8fef843e0f06108d230c40bda5db7a95932568d37cbd610eaf09acc23
+size 62392
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_6,NEXUS_5,1.0,en].png
index 4d09e6d3d2..53795c55fd 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_6,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.invite_null_DefaultGroup_RoomInviteMembersLightPreview_0_null_6,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:ca9a896e2dd3cf47792467f052f2d45b555d99ccbd91436ef4c03dfca66836ab
-size 47542
+oid sha256:307bfb8ffa9088d7d474c0d51e5e8d54bd6598b527357fe67380c755c2af75f0
+size 48893
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_2,NEXUS_5,1.0,en].png
index d2b6b99b87..eef6fe679e 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c3e225aea9018c660e16ce5ee6e1e343b439b96264b274c5d722b05c6c9a5d48
-size 52064
+oid sha256:53015a9106ad2c3a24b9073e142db6fb3ba0e40ef3e87b1ba2142ec04066eba6
+size 53644
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_3,NEXUS_5,1.0,en].png
index f056965480..d2b6b99b87 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:2372fe63e9fcc2c6529622270592437b880211acdcf7ef8be36dfc9fe41e0e5b
-size 66807
+oid sha256:c3e225aea9018c660e16ce5ee6e1e343b439b96264b274c5d722b05c6c9a5d48
+size 52064
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_4,NEXUS_5,1.0,en].png
index da20925ed9..f056965480 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_4,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_4,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:dce5d8c8dacb7c095cd22d590fc4fd9bc2cb9b13e0d4edbdd52387226b37b4ec
-size 70253
+oid sha256:2372fe63e9fcc2c6529622270592437b880211acdcf7ef8be36dfc9fe41e0e5b
+size 66807
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_5,NEXUS_5,1.0,en].png
index dd6890cc46..da20925ed9 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_5,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_5,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:d3a1afc10771257294d9f22d26ed8a81d04cd4b1f31d9fb9fc1065a8216230a9
-size 67658
+oid sha256:dce5d8c8dacb7c095cd22d590fc4fd9bc2cb9b13e0d4edbdd52387226b37b4ec
+size 70253
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_7,NEXUS_5,1.0,en].png
index 29b02fac10..dd6890cc46 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_7,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_7,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:7a3b23895b133b7bba7300fbf31fd70daa83f558e29e8c09dfc7789ce0bc04d6
-size 61861
+oid sha256:d3a1afc10771257294d9f22d26ed8a81d04cd4b1f31d9fb9fc1065a8216230a9
+size 67658
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_8,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..3f1e6f2204
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_8,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8c3317c3c03399d5575a089029e237c766b912bb80a96a8feccb005f4e7247f5
+size 63166
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_9,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..98ca67f5aa
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_9,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f3dd0fc6e0024160c03749948e4d4c6b353e3b5162b6ecc219c8bd16c2ad9848
+size 71157
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_2,NEXUS_5,1.0,en].png
index 66e16dc49c..aa5bcb484a 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:b22c207ac5f185c1c7eaa30ffe43acb2a1e8da06379f15f66ff96797b8ea9158
-size 48297
+oid sha256:7045a9ced4691ec52451b6a31d2454ddc82c04b767c96a3dd4c3f81addcb8f0c
+size 50010
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_3,NEXUS_5,1.0,en].png
index e58e13b108..66e16dc49c 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:a7a98c0125cc58a130b8ebfbd5f2b8fcfa65e02b0c6a151c0b594208a8801c96
-size 60894
+oid sha256:b22c207ac5f185c1c7eaa30ffe43acb2a1e8da06379f15f66ff96797b8ea9158
+size 48297
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_4,NEXUS_5,1.0,en].png
index b8abed4961..e58e13b108 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_4,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_4,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:998819b0aeb15712bb53e982e3756cf2e4a4069a2a13f7955e10b873e3e736bd
-size 63698
+oid sha256:a7a98c0125cc58a130b8ebfbd5f2b8fcfa65e02b0c6a151c0b594208a8801c96
+size 60894
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_5,NEXUS_5,1.0,en].png
index 5130e63f43..b8abed4961 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_5,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_5,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:cb8dc0ca3fa34ffa40ea43934ee00d0c48c9b7ce721274763c093eee83d8cff9
-size 62187
+oid sha256:998819b0aeb15712bb53e982e3756cf2e4a4069a2a13f7955e10b873e3e736bd
+size 63698
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_7,NEXUS_5,1.0,en].png
index 81d82fd9d8..5130e63f43 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_7,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_7,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:e403d076b9d28e7fd06493399d4b7c405d99f329fb1a298c41fb445a1e9ec466
-size 57215
+oid sha256:cb8dc0ca3fa34ffa40ea43934ee00d0c48c9b7ce721274763c093eee83d8cff9
+size 62187
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_8,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..451dc8f651
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_8,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:da8ee4d42e3fcc4b68e06406bee859634c245baeb9ab7850427fffc04f41f1f8
+size 58493
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_9,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..be582845d0
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_9,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1797f9c90a79fc9bcbb1aa8d8ae32452c76d17b12e8ef7f441e1c93e9ac95c3f
+size 64593
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components_null_DefaultGroup_LabelledTextFieldDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components_null_DefaultGroup_LabelledTextFieldDarkPreview_0_null,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..1de39b0f80
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components_null_DefaultGroup_LabelledTextFieldDarkPreview_0_null,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7cc0a54fec11686b8ffcba432562cfc1115973fab77693f8983a5b81a64e5d78
+size 15683
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components_null_DefaultGroup_LabelledTextFieldLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components_null_DefaultGroup_LabelledTextFieldLightPreview_0_null,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..f40b7dae38
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components_null_DefaultGroup_LabelledTextFieldLightPreview_0_null,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:738fd87b80036a040ceaddbad06ed4657a0d8d9d8c6052933300e5c194c17f24
+size 15724
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom.avatar_null_DefaultGroup_SheetContentDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.matrix.ui.components_null_DefaultGroup_AvatarActionBottomSheetDarkPreview_0_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom.avatar_null_DefaultGroup_SheetContentDarkPreview_0_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.matrix.ui.components_null_DefaultGroup_AvatarActionBottomSheetDarkPreview_0_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom.avatar_null_DefaultGroup_SheetContentLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.matrix.ui.components_null_DefaultGroup_AvatarActionBottomSheetLightPreview_0_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom.avatar_null_DefaultGroup_SheetContentLightPreview_0_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.matrix.ui.components_null_DefaultGroup_AvatarActionBottomSheetLightPreview_0_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.components_null_DefaultGroup_AvatarDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.matrix.ui.components_null_DefaultGroup_UnsavedAvatarDarkPreview_0_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.components_null_DefaultGroup_AvatarDarkPreview_0_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.matrix.ui.components_null_DefaultGroup_UnsavedAvatarDarkPreview_0_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.components_null_DefaultGroup_AvatarLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.matrix.ui.components_null_DefaultGroup_UnsavedAvatarLightPreview_0_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.components_null_DefaultGroup_AvatarLightPreview_0_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.matrix.ui.components_null_DefaultGroup_UnsavedAvatarLightPreview_0_null,NEXUS_5,1.0,en].png