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