From dfe18168a24272ef95955b32e6a8b356d67c0628 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 13 Nov 2024 17:25:05 +0100 Subject: [PATCH 1/2] create room : improve handling of room address --- .../createroom/impl/CreateRoomConfig.kt | 4 +- .../createroom/impl/CreateRoomDataStore.kt | 21 ++-- .../configureroom/ConfigureRoomPresenter.kt | 60 ++++++++++-- .../impl/configureroom/ConfigureRoomState.kt | 6 +- .../ConfigureRoomStateProvider.kt | 41 +++++++- .../impl/configureroom/ConfigureRoomView.kt | 17 +++- .../configureroom/RoomAddressErrorState.kt | 17 ---- .../impl/configureroom/RoomAddressValidity.kt | 26 +++++ .../impl/configureroom/RoomVisibilityState.kt | 8 -- .../impl/addpeople/AddPeoplePresenterTest.kt | 3 +- .../ConfigureRoomPresenterTest.kt | 97 +++++++++++++++++-- ...est.kt => RoomAliasHelperPresenterTest.kt} | 2 +- ...ViewTest.kt => RoomAliasHelperViewTest.kt} | 2 +- .../libraries/matrix/api/MatrixClient.kt | 10 ++ .../api/createroom/CreateRoomParameters.kt | 2 +- .../matrix/api/room/alias/RoomAliasHelper.kt | 15 +++ .../libraries/matrix/impl/RustMatrixClient.kt | 2 +- .../impl/room/alias/DefaultRoomAliasHelper.kt | 25 +++++ .../test/room/alias/FakeRoomAliasHelper.kt | 26 +++++ 19 files changed, 320 insertions(+), 64 deletions(-) delete mode 100644 features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAddressErrorState.kt create mode 100644 features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAddressValidity.kt rename features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/{RoomAliasResolverPresenterTest.kt => RoomAliasHelperPresenterTest.kt} (98%) rename features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/{RoomAliasResolverViewTest.kt => RoomAliasHelperViewTest.kt} (98%) create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/alias/RoomAliasHelper.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/alias/DefaultRoomAliasHelper.kt create mode 100644 libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/alias/FakeRoomAliasHelper.kt diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomConfig.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomConfig.kt index ab6e116598..a83673886d 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomConfig.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomConfig.kt @@ -19,6 +19,4 @@ data class CreateRoomConfig( val avatarUri: Uri? = null, val invites: ImmutableList = persistentListOf(), val roomVisibility: RoomVisibilityState = RoomVisibilityState.Private, -) { - val isValid = roomName.isNullOrEmpty().not() && roomVisibility.isValid() -} +) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomDataStore.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomDataStore.kt index 56ebe326dd..67e697e8e4 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomDataStore.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomDataStore.kt @@ -11,13 +11,13 @@ import android.net.Uri import io.element.android.features.createroom.impl.configureroom.RoomAccess import io.element.android.features.createroom.impl.configureroom.RoomAccessItem import io.element.android.features.createroom.impl.configureroom.RoomAddress -import io.element.android.features.createroom.impl.configureroom.RoomAddressErrorState import io.element.android.features.createroom.impl.configureroom.RoomVisibilityItem import io.element.android.features.createroom.impl.configureroom.RoomVisibilityState import io.element.android.features.createroom.impl.di.CreateRoomScope import io.element.android.features.createroom.impl.userlist.UserListDataStore import io.element.android.libraries.androidutils.file.safeDelete import io.element.android.libraries.di.SingleIn +import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -29,6 +29,7 @@ import javax.inject.Inject @SingleIn(CreateRoomScope::class) class CreateRoomDataStore @Inject constructor( val selectedUserListDataStore: UserListDataStore, + private val roomAliasHelper: RoomAliasHelper, ) { private val createRoomConfigFlow: MutableStateFlow = MutableStateFlow(CreateRoomConfig()) private var cachedAvatarUri: Uri? = null @@ -46,13 +47,13 @@ class CreateRoomDataStore @Inject constructor( fun setRoomName(roomName: String) { createRoomConfigFlow.getAndUpdate { config -> - /* val newVisibility = when (config.roomVisibility) { is RoomVisibilityState.Public -> { val roomAddress = config.roomVisibility.roomAddress if (roomAddress is RoomAddress.AutoFilled || roomName.isEmpty()) { + val roomAliasName = roomAliasHelper.roomAliasNameFromRoomDisplayName(roomName) config.roomVisibility.copy( - roomAddress = RoomAddress.AutoFilled(roomName), + roomAddress = RoomAddress.AutoFilled(roomAliasName), ) } else { config.roomVisibility @@ -60,9 +61,9 @@ class CreateRoomDataStore @Inject constructor( } else -> config.roomVisibility } - */ config.copy( roomName = roomName.takeIf { it.isNotEmpty() }, + roomVisibility = newVisibility, ) } } @@ -85,11 +86,13 @@ class CreateRoomDataStore @Inject constructor( config.copy( roomVisibility = when (visibility) { RoomVisibilityItem.Private -> RoomVisibilityState.Private - RoomVisibilityItem.Public -> RoomVisibilityState.Public( - roomAddress = RoomAddress.AutoFilled(config.roomName.orEmpty()), - roomAddressErrorState = RoomAddressErrorState.None, - roomAccess = RoomAccess.Anyone, - ) + RoomVisibilityItem.Public -> { + val roomAliasName = roomAliasHelper.roomAliasNameFromRoomDisplayName(config.roomName.orEmpty()) + RoomVisibilityState.Public( + roomAddress = RoomAddress.AutoFilled(roomAliasName), + roomAccess = RoomAccess.Anyone, + ) + } } ) } 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 bbefc987ef..3ace410c78 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 @@ -17,6 +17,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.rememberUpdatedState import im.vector.app.features.analytics.plan.CreatedRoom import io.element.android.features.createroom.impl.CreateRoomConfig import io.element.android.features.createroom.impl.CreateRoomDataStore @@ -31,6 +32,8 @@ 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.api.room.alias.RoomAliasHelper +import io.element.android.libraries.matrix.api.roomAliasFromName 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 @@ -39,9 +42,12 @@ import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.services.analytics.api.AnalyticsService import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import timber.log.Timber +import java.util.Optional import javax.inject.Inject +import kotlin.jvm.optionals.getOrNull class ConfigureRoomPresenter @Inject constructor( private val dataStore: CreateRoomDataStore, @@ -51,6 +57,7 @@ class ConfigureRoomPresenter @Inject constructor( private val analyticsService: AnalyticsService, permissionsPresenterFactory: PermissionsPresenter.Factory, private val featureFlagService: FeatureFlagService, + private val roomAliasHelper: RoomAliasHelper, ) : Presenter { private val cameraPermissionPresenter: PermissionsPresenter = permissionsPresenterFactory.create(android.Manifest.permission.CAMERA) private var pendingPermissionRequest = false @@ -58,9 +65,12 @@ class ConfigureRoomPresenter @Inject constructor( @Composable override fun present(): ConfigureRoomState { val cameraPermissionState = cameraPermissionPresenter.present() - val createRoomConfig = dataStore.createRoomConfigWithInvites.collectAsState(CreateRoomConfig()) + val createRoomConfig by dataStore.createRoomConfigWithInvites.collectAsState(CreateRoomConfig()) val homeserverName = remember { matrixClient.userIdServerName() } val isKnockFeatureEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock).collectAsState(initial = false) + val roomAddressValidity = remember { + mutableStateOf(RoomAddressValidity.Unknown) + } val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker( onResult = { uri -> if (uri != null) dataStore.setAvatarUri(uri = uri, cached = true) }, @@ -69,12 +79,12 @@ class ConfigureRoomPresenter @Inject constructor( onResult = { uri -> if (uri != null) dataStore.setAvatarUri(uri = uri) } ) - val avatarActions by remember(createRoomConfig.value.avatarUri) { + val avatarActions by remember(createRoomConfig.avatarUri) { derivedStateOf { listOfNotNull( AvatarAction.TakePhoto, AvatarAction.ChoosePhoto, - AvatarAction.Remove.takeIf { createRoomConfig.value.avatarUri != null }, + AvatarAction.Remove.takeIf { createRoomConfig.avatarUri != null }, ).toImmutableList() } } @@ -86,6 +96,10 @@ class ConfigureRoomPresenter @Inject constructor( } } + RoomAddressValidityEffect(createRoomConfig.roomVisibility.roomAddress()) { newRoomAddressValidity -> + roomAddressValidity.value = newRoomAddressValidity + } + val localCoroutineScope = rememberCoroutineScope() val createRoomAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } @@ -102,7 +116,7 @@ class ConfigureRoomPresenter @Inject constructor( is ConfigureRoomEvents.RemoveUserFromSelection -> dataStore.selectedUserListDataStore.removeUserFromSelection(event.matrixUser) is ConfigureRoomEvents.RoomAccessChanged -> dataStore.setRoomAccess(event.roomAccess) is ConfigureRoomEvents.RoomAddressChanged -> dataStore.setRoomAddress(event.roomAddress) - is ConfigureRoomEvents.CreateRoom -> createRoom(createRoomConfig.value) + is ConfigureRoomEvents.CreateRoom -> createRoom(createRoomConfig) is ConfigureRoomEvents.HandleAvatarAction -> { when (event.action) { AvatarAction.ChoosePhoto -> galleryImagePicker.launch() @@ -122,15 +136,49 @@ class ConfigureRoomPresenter @Inject constructor( return ConfigureRoomState( isKnockFeatureEnabled = isKnockFeatureEnabled, - config = createRoomConfig.value, + config = createRoomConfig, avatarActions = avatarActions, createRoomAction = createRoomAction.value, cameraPermissionState = cameraPermissionState, homeserverName = homeserverName, + roomAddressValidity = roomAddressValidity.value, eventSink = ::handleEvents, ) } + @Composable + private fun RoomAddressValidityEffect( + roomAddress: Optional, + onRoomAddressValidityChange: (RoomAddressValidity) -> Unit, + ) { + val onChange by rememberUpdatedState(onRoomAddressValidityChange) + LaunchedEffect(roomAddress) { + val roomAliasName = roomAddress.getOrNull().orEmpty() + if (roomAliasName.isEmpty()) { + onChange(RoomAddressValidity.Unknown) + return@LaunchedEffect + } + // debounce the room address validation + delay(300) + val roomAlias = matrixClient.roomAliasFromName(roomAliasName).getOrNull() + if (roomAlias == null || !roomAliasHelper.isRoomAliasValid(roomAlias)) { + onChange(RoomAddressValidity.InvalidSymbols) + } else { + matrixClient.resolveRoomAlias(roomAlias) + .onSuccess { resolved -> + if (resolved.isPresent) { + onChange(RoomAddressValidity.NotAvailable) + } else { + onChange(RoomAddressValidity.Valid) + } + } + .onFailure { + onChange(RoomAddressValidity.Valid) + } + } + } + } + private fun CoroutineScope.createRoom( config: CreateRoomConfig, createRoomAction: MutableState> @@ -148,7 +196,7 @@ class ConfigureRoomPresenter @Inject constructor( preset = RoomPreset.PUBLIC_CHAT, invite = config.invites.map { it.userId }, avatar = avatarUrl, - canonicalAlias = config.roomVisibility.roomAddress() + roomAliasName = config.roomVisibility.roomAddress() ) } else { CreateRoomParameters( 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 8a2122c306..2730efb19f 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 @@ -20,6 +20,10 @@ data class ConfigureRoomState( val avatarActions: ImmutableList, val createRoomAction: AsyncAction, val cameraPermissionState: PermissionsState, + val roomAddressValidity: RoomAddressValidity, val homeserverName: String, val eventSink: (ConfigureRoomEvents) -> Unit -) +) { + val isValid: Boolean = config.roomName?.isNotEmpty() == true && + (config.roomVisibility is RoomVisibilityState.Private || roomAddressValidity == RoomAddressValidity.Valid) +} diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStateProvider.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStateProvider.kt index 80445fbff4..0de6404424 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStateProvider.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStateProvider.kt @@ -28,9 +28,8 @@ open class ConfigureRoomStateProvider : PreviewParameterProvider = AsyncAction.Uninitialized, cameraPermissionState: PermissionsState = aPermissionsState(showDialog = false), homeserverName: String = "matrix.org", + roomAddressValidity: RoomAddressValidity = RoomAddressValidity.Valid, eventSink: (ConfigureRoomEvents) -> Unit = { }, ) = ConfigureRoomState( config = config, @@ -64,5 +96,6 @@ fun aConfigureRoomState( createRoomAction = createRoomAction, cameraPermissionState = cameraPermissionState, homeserverName = homeserverName, + roomAddressValidity = roomAddressValidity, eventSink = eventSink, ) 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 c832fab464..e34bb27a8b 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 @@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.imePadding @@ -79,7 +80,7 @@ fun ConfigureRoomView( modifier = modifier.clearFocusOnTap(focusManager), topBar = { ConfigureRoomToolbar( - isNextActionEnabled = state.config.isValid, + isNextActionEnabled = state.isValid, onBackClick = onBackClick, onNextClick = { focusManager.clearFocus() @@ -143,8 +144,10 @@ fun ConfigureRoomView( modifier = Modifier.padding(horizontal = 16.dp), address = state.config.roomVisibility.roomAddress, homeserverName = state.homeserverName, + addressValidity = state.roomAddressValidity, onAddressChange = { state.eventSink(ConfigureRoomEvents.RoomAddressChanged(it)) }, ) + Spacer(Modifier) } } } @@ -319,6 +322,7 @@ private fun RoomAccessOptions( private fun RoomAddressField( address: RoomAddress, homeserverName: String, + addressValidity: RoomAddressValidity, onAddressChange: (String) -> Unit, modifier: Modifier = Modifier, ) { @@ -340,7 +344,16 @@ private fun RoomAddressField( color = ElementTheme.colors.textSecondary, ) }, - supportingText = stringResource(R.string.screen_create_room_room_address_section_footer), + supportingText = when (addressValidity) { + RoomAddressValidity.InvalidSymbols -> { + stringResource(R.string.screen_create_room_room_address_invalid_symbols_error_description) + } + RoomAddressValidity.NotAvailable -> { + stringResource(R.string.screen_create_room_room_address_not_available_error_description) + } + else -> stringResource(R.string.screen_create_room_room_address_section_footer) + }, + isError = addressValidity.isError(), onValueChange = onAddressChange, singleLine = true, ) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAddressErrorState.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAddressErrorState.kt deleted file mode 100644 index f2cadfd6bb..0000000000 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAddressErrorState.kt +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.features.createroom.impl.configureroom - -/** - * Represents the error state of a room address. - */ -sealed interface RoomAddressErrorState { - data object InvalidCharacters : RoomAddressErrorState - data object AlreadyExists : RoomAddressErrorState - data object None : RoomAddressErrorState -} diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAddressValidity.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAddressValidity.kt new file mode 100644 index 0000000000..536f48f94e --- /dev/null +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAddressValidity.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.createroom.impl.configureroom + +import androidx.compose.runtime.Immutable + +/** + * Represents the validity state of a room address. + * ie. whether it contains invalid characters, is already taken, or is valid. + */ +@Immutable +sealed interface RoomAddressValidity { + data object Unknown : RoomAddressValidity + data object InvalidSymbols : RoomAddressValidity + data object NotAvailable : RoomAddressValidity + data object Valid : RoomAddressValidity + + fun isError(): Boolean { + return this is InvalidSymbols || this is NotAvailable + } +} diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomVisibilityState.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomVisibilityState.kt index cc5af7836e..502e307557 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomVisibilityState.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomVisibilityState.kt @@ -14,7 +14,6 @@ sealed interface RoomVisibilityState { data class Public( val roomAddress: RoomAddress, - val roomAddressErrorState: RoomAddressErrorState, val roomAccess: RoomAccess, ) : RoomVisibilityState @@ -24,11 +23,4 @@ sealed interface RoomVisibilityState { is Public -> Optional.of(roomAddress.value) } } - - fun isValid(): Boolean { - return when (this) { - is Private -> true - is Public -> roomAddressErrorState is RoomAddressErrorState.None && roomAddress.value.isNotEmpty() - } - } } diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeoplePresenterTest.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeoplePresenterTest.kt index 7a9ae7321e..69205e6178 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeoplePresenterTest.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeoplePresenterTest.kt @@ -14,6 +14,7 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.createroom.impl.CreateRoomDataStore import io.element.android.features.createroom.impl.userlist.FakeUserListPresenterFactory import io.element.android.features.createroom.impl.userlist.UserListDataStore +import io.element.android.libraries.matrix.test.room.alias.FakeRoomAliasHelper import io.element.android.libraries.usersearch.test.FakeUserRepository import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest @@ -32,7 +33,7 @@ class AddPeoplePresenterTest { presenter = AddPeoplePresenter( FakeUserListPresenterFactory(), FakeUserRepository(), - CreateRoomDataStore(UserListDataStore()) + CreateRoomDataStore(UserListDataStore(), FakeRoomAliasHelper()) ) } diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTest.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTest.kt index fefb09c241..b39e08ec80 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTest.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTest.kt @@ -19,11 +19,15 @@ import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias +import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper import io.element.android.libraries.matrix.test.AN_AVATAR_URL import io.element.android.libraries.matrix.test.A_MESSAGE +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_THROWABLE import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.matrix.test.room.alias.FakeRoomAliasHelper import io.element.android.libraries.matrix.ui.components.aMatrixUser import io.element.android.libraries.matrix.ui.media.AvatarAction import io.element.android.libraries.mediapickers.api.PickerProvider @@ -44,6 +48,8 @@ import io.mockk.mockkStatic import io.mockk.unmockkAll import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Before @@ -52,6 +58,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner import java.io.File +import java.util.Optional private const val AN_URI_FROM_CAMERA = "content://uri_from_camera" private const val AN_URI_FROM_CAMERA_2 = "content://uri_from_camera_2" @@ -95,21 +102,21 @@ class ConfigureRoomPresenterTest { presenter.test { val initialState = initialState() var config = initialState.config - assertThat(initialState.config.isValid).isFalse() + assertThat(initialState.isValid).isFalse() // Room name not empty initialState.eventSink(ConfigureRoomEvents.RoomNameChanged(A_ROOM_NAME)) var newState: ConfigureRoomState = awaitItem() config = config.copy(roomName = A_ROOM_NAME) assertThat(newState.config).isEqualTo(config) - assertThat(newState.config.isValid).isTrue() + assertThat(newState.isValid).isTrue() // Clear room name newState.eventSink(ConfigureRoomEvents.RoomNameChanged("")) newState = awaitItem() config = config.copy(roomName = null) assertThat(newState.config).isEqualTo(config) - assertThat(newState.config.isValid).isFalse() + assertThat(newState.isValid).isFalse() } } @@ -118,8 +125,9 @@ class ConfigureRoomPresenterTest { val userListDataStore = UserListDataStore() val pickerProvider = FakePickerProvider() val permissionsPresenter = FakePermissionsPresenter() + val roomAliasHelper = FakeRoomAliasHelper() val presenter = createConfigureRoomPresenter( - createRoomDataStore = CreateRoomDataStore(userListDataStore), + createRoomDataStore = CreateRoomDataStore(userListDataStore, roomAliasHelper), pickerProvider = pickerProvider, permissionsPresenter = permissionsPresenter, ) @@ -191,8 +199,7 @@ class ConfigureRoomPresenterTest { newState = awaitItem() expectedConfig = expectedConfig.copy( roomVisibility = RoomVisibilityState.Public( - roomAddress = RoomAddress.AutoFilled(expectedConfig.roomName ?: ""), - roomAddressErrorState = RoomAddressErrorState.None, + roomAddress = RoomAddress.AutoFilled(roomAliasHelper.roomAliasNameFromRoomDisplayName(expectedConfig.roomName ?: "")), roomAccess = RoomAccess.Anyone, ) ) @@ -254,7 +261,7 @@ class ConfigureRoomPresenterTest { val matrixClient = createMatrixClient() val analyticsService = FakeAnalyticsService() val mediaPreProcessor = FakeMediaPreProcessor() - val createRoomDataStore = CreateRoomDataStore(UserListDataStore()) + val createRoomDataStore = CreateRoomDataStore(UserListDataStore(), FakeRoomAliasHelper()) val presenter = createConfigureRoomPresenter( createRoomDataStore = createRoomDataStore, mediaPreProcessor = mediaPreProcessor, @@ -315,17 +322,88 @@ class ConfigureRoomPresenterTest { } } + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun `present - address is invalid when format is invalid`() = runTest { + val aliasHelper = FakeRoomAliasHelper( + isRoomAliasValidLambda = { false } + ) + val presenter = createConfigureRoomPresenter( + roomAliasHelper = aliasHelper + ) + presenter.test { + val initialState = initialState() + initialState.eventSink(ConfigureRoomEvents.RoomVisibilityChanged(RoomVisibilityItem.Public)) + skipItems(1) + initialState.eventSink(ConfigureRoomEvents.RoomAddressChanged("invalid address")) + skipItems(1) + advanceUntilIdle() + awaitItem().also { state -> + assertThat(state.roomAddressValidity).isEqualTo(RoomAddressValidity.InvalidSymbols) + } + } + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun `present - address is not available when alias is not available`() = runTest { + val fakeMatrixClient = createMatrixClient(isAliasAvailable = false) + val presenter = createConfigureRoomPresenter( + matrixClient = fakeMatrixClient, + ) + presenter.test { + val initialState = initialState() + initialState.eventSink(ConfigureRoomEvents.RoomVisibilityChanged(RoomVisibilityItem.Public)) + skipItems(1) + initialState.eventSink(ConfigureRoomEvents.RoomAddressChanged("address")) + skipItems(1) + advanceUntilIdle() + awaitItem().also { state -> + assertThat(state.roomAddressValidity).isEqualTo(RoomAddressValidity.NotAvailable) + } + } + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun `present - address is valid when alias is available and format is valid`() = runTest { + val fakeMatrixClient = createMatrixClient(isAliasAvailable = true) + val presenter = createConfigureRoomPresenter( + matrixClient = fakeMatrixClient, + ) + presenter.test { + val initialState = initialState() + initialState.eventSink(ConfigureRoomEvents.RoomVisibilityChanged(RoomVisibilityItem.Public)) + skipItems(1) + initialState.eventSink(ConfigureRoomEvents.RoomAddressChanged("address")) + skipItems(1) + advanceUntilIdle() + awaitItem().also { state -> + assertThat(state.roomAddressValidity).isEqualTo(RoomAddressValidity.Valid) + } + } + } + private suspend fun TurbineTestContext.initialState(): ConfigureRoomState { skipItems(1) return awaitItem() } - private fun createMatrixClient() = FakeMatrixClient( + private fun createMatrixClient(isAliasAvailable: Boolean = true) = FakeMatrixClient( userIdServerNameLambda = { "matrix.org" }, + resolveRoomAliasResult = { + val resolvedRoomAlias = if (isAliasAvailable) { + Optional.empty() + } else { + Optional.of(ResolvedRoomAlias(A_ROOM_ID, emptyList())) + } + Result.success(resolvedRoomAlias) + } ) private fun createConfigureRoomPresenter( - createRoomDataStore: CreateRoomDataStore = CreateRoomDataStore(UserListDataStore()), + roomAliasHelper: RoomAliasHelper = FakeRoomAliasHelper(), + createRoomDataStore: CreateRoomDataStore = CreateRoomDataStore(UserListDataStore(), roomAliasHelper), matrixClient: MatrixClient = createMatrixClient(), pickerProvider: PickerProvider = FakePickerProvider(), mediaPreProcessor: MediaPreProcessor = FakeMediaPreProcessor(), @@ -339,6 +417,7 @@ class ConfigureRoomPresenterTest { mediaPreProcessor = mediaPreProcessor, analyticsService = analyticsService, permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionsPresenter), + roomAliasHelper = roomAliasHelper, featureFlagService = FakeFeatureFlagService( mapOf(FeatureFlags.Knock.key to isKnockFeatureEnabled) ) diff --git a/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverPresenterTest.kt b/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasHelperPresenterTest.kt similarity index 98% rename from features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverPresenterTest.kt rename to features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasHelperPresenterTest.kt index 9894c2b342..ab4d9bc8a6 100644 --- a/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverPresenterTest.kt +++ b/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasHelperPresenterTest.kt @@ -26,7 +26,7 @@ import org.junit.Rule import org.junit.Test import java.util.Optional -class RoomAliasResolverPresenterTest { +class RoomAliasHelperPresenterTest { @get:Rule val warmUpRule = WarmUpRule() diff --git a/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverViewTest.kt b/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasHelperViewTest.kt similarity index 98% rename from features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverViewTest.kt rename to features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasHelperViewTest.kt index 6fc3aa64a2..7f4f26cc3d 100644 --- a/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverViewTest.kt +++ b/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasHelperViewTest.kt @@ -27,7 +27,7 @@ import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) -class RoomAliasResolverViewTest { +class RoomAliasHelperViewTest { @get:Rule val rule = createAndroidComposeRule() @Test diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index 504db1c6d9..eca03afb0c 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -168,3 +168,13 @@ fun MatrixClient.getRoomInfoFlow(roomIdOrAlias: RoomIdOrAlias): Flow roomSummary.map { it.info } } .distinctUntilChanged() } + +/** + * Returns a room alias from a room alias name. + * @param name the room alias name ie. the local part of the room alias. + */ +fun MatrixClient.roomAliasFromName(name: String): Result { + return runCatching { + RoomAlias("#$name:${userIdServerName()}") + } +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/createroom/CreateRoomParameters.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/createroom/CreateRoomParameters.kt index 2e13623c96..93ac71ef17 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/createroom/CreateRoomParameters.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/createroom/CreateRoomParameters.kt @@ -20,5 +20,5 @@ data class CreateRoomParameters( val invite: List? = null, val avatar: String? = null, val joinRuleOverride: JoinRuleOverride = JoinRuleOverride.None, - val canonicalAlias: Optional = Optional.empty(), + val roomAliasName: Optional = Optional.empty(), ) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/alias/RoomAliasHelper.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/alias/RoomAliasHelper.kt new file mode 100644 index 0000000000..eb746e1f0d --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/alias/RoomAliasHelper.kt @@ -0,0 +1,15 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.matrix.api.room.alias + +import io.element.android.libraries.matrix.api.core.RoomAlias + +interface RoomAliasHelper { + fun roomAliasNameFromRoomDisplayName(name: String): String + fun isRoomAliasValid(roomAlias: RoomAlias): Boolean +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index 3165cef1bf..dc852c900d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -334,7 +334,7 @@ class RustMatrixClient( JoinRuleOverride.Knock -> RustJoinRule.Knock JoinRuleOverride.None -> null }, - canonicalAlias = createRoomParams.canonicalAlias.getOrNull(), + canonicalAlias = createRoomParams.roomAliasName.getOrNull(), ) val roomId = RoomId(client.createRoom(rustParams)) // Wait to receive the room back from the sync but do not returns failure if it fails. diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/alias/DefaultRoomAliasHelper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/alias/DefaultRoomAliasHelper.kt new file mode 100644 index 0000000000..a2fa36f65c --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/alias/DefaultRoomAliasHelper.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.room.alias + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.matrix.api.core.RoomAlias +import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultRoomAliasHelper @Inject constructor() : RoomAliasHelper { + override fun roomAliasNameFromRoomDisplayName(name: String): String { + return org.matrix.rustcomponents.sdk.roomAliasNameFromRoomDisplayName(name) + } + + override fun isRoomAliasValid(roomAlias: RoomAlias): Boolean { + return org.matrix.rustcomponents.sdk.isRoomAliasFormatValid(roomAlias.value) + } +} diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/alias/FakeRoomAliasHelper.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/alias/FakeRoomAliasHelper.kt new file mode 100644 index 0000000000..d832b9de5a --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/alias/FakeRoomAliasHelper.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.matrix.test.room.alias + +import io.element.android.libraries.matrix.api.core.RoomAlias +import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper + +class FakeRoomAliasHelper( + private val roomAliasNameFromRoomDisplayNameLambda: (String) -> String = { name -> + name.trimStart().trimEnd().replace(" ", "_") + }, + private val isRoomAliasValidLambda: (RoomAlias) -> Boolean = { true } +) : RoomAliasHelper { + override fun roomAliasNameFromRoomDisplayName(name: String): String { + return roomAliasNameFromRoomDisplayNameLambda(name) + } + + override fun isRoomAliasValid(roomAlias: RoomAlias): Boolean { + return isRoomAliasValidLambda(roomAlias) + } +} From e63fa1f8c0f17e549c860833ed4c14289d116a9b Mon Sep 17 00:00:00 2001 From: ElementBot Date: Wed, 13 Nov 2024 17:07:27 +0000 Subject: [PATCH 2/2] Update screenshots --- ...eateroom.impl.configureroom_ConfigureRoomViewDark_2_en.png | 4 ++-- ...eateroom.impl.configureroom_ConfigureRoomViewDark_3_en.png | 3 +++ ...eateroom.impl.configureroom_ConfigureRoomViewDark_4_en.png | 3 +++ ...eateroom.impl.configureroom_ConfigureRoomViewDark_5_en.png | 3 +++ ...ateroom.impl.configureroom_ConfigureRoomViewLight_2_en.png | 4 ++-- ...ateroom.impl.configureroom_ConfigureRoomViewLight_3_en.png | 3 +++ ...ateroom.impl.configureroom_ConfigureRoomViewLight_4_en.png | 3 +++ ...ateroom.impl.configureroom_ConfigureRoomViewLight_5_en.png | 3 +++ 8 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewDark_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewDark_4_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewDark_5_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewLight_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewLight_4_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewLight_5_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewDark_2_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewDark_2_en.png index 80f53da42a..9bcb3813ac 100644 --- a/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewDark_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewDark_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e6270f3b7935c47da76ce4e81aa7e8eac13a9340e7ccb198f8cd6956d746dfa8 -size 59452 +oid sha256:ffbdcd28c32808fba8ffb7071b6440578716d3ae89149eb319c22d3f3b78b1ff +size 59499 diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewDark_3_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewDark_3_en.png new file mode 100644 index 0000000000..e9d68af4ee --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewDark_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ccbb514ad9bd6a95ecff2e278574ac5bff9bd8a63a5fc91fa35302dbaa9e71f2 +size 55276 diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewDark_4_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewDark_4_en.png new file mode 100644 index 0000000000..43814bce07 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewDark_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a2ea9839e3dd1288d99c2115c37bc7fe72716af9785c4de3e328a77a57d12d70 +size 56633 diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewDark_5_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewDark_5_en.png new file mode 100644 index 0000000000..b254eeceb3 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewDark_5_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b28da0e8ff5cbedf8777b5af8ff6bab2bd5c476df80878b2c28dd96a354d978 +size 54383 diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewLight_2_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewLight_2_en.png index 397dd1dfb7..bd45e398b4 100644 --- a/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewLight_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewLight_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c3b02b0bc0d557bac4d5aa945daf04d5d58dab44d9e4284d030804e6b5ed6a49 -size 60703 +oid sha256:9dc54d0a6c3cb3069482b1c27ed6d80fd63b0bcad1a550bf5c4edb09f69db3a7 +size 60749 diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewLight_3_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewLight_3_en.png new file mode 100644 index 0000000000..ae54d119d7 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewLight_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:71c42c595070733ac8ac2c50da116e73ea6eea7dc120f6f0d28022b5a5f925bd +size 57099 diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewLight_4_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewLight_4_en.png new file mode 100644 index 0000000000..9229891a5f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewLight_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e10c1c3f2b4045d20c9513c70bc4ea041e091976df5385b93d45c2d03b8f6e5f +size 58513 diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewLight_5_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewLight_5_en.png new file mode 100644 index 0000000000..a9ab02f20e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewLight_5_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb54ae79de75af32e32642063d9dc9e0c24b0089f187a043f0fc330151c72c75 +size 56170