diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 9ae5cec637..f78f3aa668 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -357,8 +357,8 @@ class LoggedInFlowNode @AssistedInject constructor( } NavTarget.CreateRoom -> { val callback = object : CreateRoomEntryPoint.Callback { - override fun onSuccess(roomId: RoomId) { - backstack.replace(NavTarget.Room(roomId.toRoomIdOrAlias())) + override fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List) { + backstack.replace(NavTarget.Room(roomIdOrAlias = roomIdOrAlias, serverNames = serverNames)) } } diff --git a/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/CreateRoomEntryPoint.kt b/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/CreateRoomEntryPoint.kt index b37c804701..c15db6dabd 100644 --- a/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/CreateRoomEntryPoint.kt +++ b/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/CreateRoomEntryPoint.kt @@ -11,7 +11,7 @@ import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import io.element.android.libraries.architecture.FeatureEntryPoint -import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.RoomIdOrAlias interface CreateRoomEntryPoint : FeatureEntryPoint { fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder @@ -21,6 +21,6 @@ interface CreateRoomEntryPoint : FeatureEntryPoint { } interface Callback : Plugin { - fun onSuccess(roomId: RoomId) + fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List) } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/CreateRoomNavigator.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/CreateRoomNavigator.kt new file mode 100644 index 0000000000..a63c99d06f --- /dev/null +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/CreateRoomNavigator.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.createroom + +import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.navmodel.backstack.BackStack +import com.bumble.appyx.navmodel.backstack.operation.push +import io.element.android.features.createroom.impl.CreateRoomFlowNode.NavTarget +import io.element.android.libraries.architecture.overlay.Overlay +import io.element.android.libraries.architecture.overlay.operation.hide +import io.element.android.libraries.architecture.overlay.operation.show +import io.element.android.libraries.matrix.api.core.RoomIdOrAlias + +interface CreateRoomNavigator : Plugin { + fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List) + fun onCreateNewRoom() + fun onShowJoinRoomByAddress() + fun onDismissJoinRoomByAddress() +} + +class DefaultCreateRoomNavigator( + private val backstack: BackStack, + private val overlay: Overlay, + private val openRoom: (RoomIdOrAlias, List) -> Unit, +) : CreateRoomNavigator { + override fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List) = openRoom(roomIdOrAlias, serverNames) + + override fun onCreateNewRoom() { + backstack.push(NavTarget.NewRoom) + } + + override fun onShowJoinRoomByAddress() { + overlay.show(NavTarget.JoinByAddress) + } + + override fun onDismissJoinRoomByAddress() { + overlay.hide() + } +} diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/ConfigureRoomFlowNode.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/ConfigureRoomFlowNode.kt index 1a2916b16a..a7678f130f 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/ConfigureRoomFlowNode.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/ConfigureRoomFlowNode.kt @@ -19,6 +19,7 @@ import com.bumble.appyx.navmodel.backstack.operation.push import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.createroom.CreateRoomNavigator import io.element.android.features.createroom.impl.addpeople.AddPeopleNode import io.element.android.features.createroom.impl.configureroom.ConfigureRoomNode import io.element.android.features.createroom.impl.di.CreateRoomComponent @@ -46,6 +47,7 @@ class ConfigureRoomFlowNode @AssistedInject constructor( private val component by lazy { parent!!.bindings().createRoomComponentBuilder().build() } + private val navigator = plugins().first() override val daggerComponent: Any get() = component @@ -69,8 +71,7 @@ class ConfigureRoomFlowNode @AssistedInject constructor( createNode(buildContext = buildContext, plugins = listOf(callback)) } NavTarget.ConfigureRoom -> { - val callbacks = plugins() - createNode(buildContext = buildContext, plugins = callbacks) + createNode(buildContext = buildContext, plugins = listOf(navigator)) } } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt index 9e06bf6080..b58ebc083e 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt @@ -8,25 +8,28 @@ package io.element.android.features.createroom.impl import android.os.Parcelable +import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.navigation.transition.JumpToEndTransitionHandler import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack -import com.bumble.appyx.navmodel.backstack.operation.push import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.createroom.DefaultCreateRoomNavigator import io.element.android.features.createroom.api.CreateRoomEntryPoint -import io.element.android.features.createroom.impl.configureroom.ConfigureRoomNode +import io.element.android.features.createroom.impl.joinbyaddress.JoinRoomByAddressNode import io.element.android.features.createroom.impl.root.CreateRoomRootNode import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.OverlayView import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.SessionScope -import io.element.android.libraries.matrix.api.core.RoomId import kotlinx.parcelize.Parcelize @ContributesNode(SessionScope::class) @@ -47,35 +50,38 @@ class CreateRoomFlowNode @AssistedInject constructor( @Parcelize data object NewRoom : NavTarget + + @Parcelize + data object JoinByAddress : NavTarget } + private val navigator = DefaultCreateRoomNavigator( + backstack = backstack, + overlay = overlay, + openRoom = { roomIdOrAlias, viaServers -> + plugins().forEach { it.onOpenRoom(roomIdOrAlias, viaServers) } + } + ) + override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { NavTarget.Root -> { - val callback = object : CreateRoomRootNode.Callback { - override fun onCreateNewRoom() { - backstack.push(NavTarget.NewRoom) - } - - override fun onStartChatSuccess(roomId: RoomId) { - plugins().forEach { it.onSuccess(roomId) } - } - } - createNode(buildContext = buildContext, plugins = listOf(callback)) + createNode(buildContext = buildContext, plugins = listOf(navigator)) } NavTarget.NewRoom -> { - val callback = object : ConfigureRoomNode.Callback { - override fun onCreateRoomSuccess(roomId: RoomId) { - plugins().forEach { it.onSuccess(roomId) } - } - } - createNode(buildContext = buildContext, plugins = listOf(callback)) + createNode(buildContext = buildContext, plugins = listOf(navigator)) + } + NavTarget.JoinByAddress -> { + createNode(buildContext = buildContext, plugins = listOf(navigator)) } } } @Composable override fun View(modifier: Modifier) { - BackstackView() + Box(modifier = modifier) { + BackstackView() + OverlayView(transitionHandler = remember { JumpToEndTransitionHandler() }) + } } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomNode.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomNode.kt index 966369e6e3..e718825163 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomNode.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomNode.kt @@ -18,8 +18,9 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import im.vector.app.features.analytics.plan.MobileScreen import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.createroom.CreateRoomNavigator import io.element.android.features.createroom.impl.di.CreateRoomScope -import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias import io.element.android.services.analytics.api.AnalyticsService @ContributesNode(CreateRoomScope::class) @@ -29,6 +30,8 @@ class ConfigureRoomNode @AssistedInject constructor( private val presenter: ConfigureRoomPresenter, private val analyticsService: AnalyticsService, ) : Node(buildContext, plugins = plugins) { + private val navigator = plugins().first() + init { lifecycle.subscribe( onResume = { @@ -37,14 +40,6 @@ class ConfigureRoomNode @AssistedInject constructor( ) } - interface Callback : Plugin { - fun onCreateRoomSuccess(roomId: RoomId) - } - - private fun onCreateRoomSuccess(roomId: RoomId) { - plugins().forEach { it.onCreateRoomSuccess(roomId) } - } - @Composable override fun View(modifier: Modifier) { val state = presenter.present() @@ -52,7 +47,9 @@ class ConfigureRoomNode @AssistedInject constructor( state = state, modifier = modifier, onBackClick = this::navigateUp, - onCreateRoomSuccess = this::onCreateRoomSuccess, + onCreateRoomSuccess = { + navigator.onOpenRoom(roomIdOrAlias = it.toRoomIdOrAlias(), serverNames = emptyList()) + }, ) } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressEvents.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressEvents.kt new file mode 100644 index 0000000000..dbbcef56fa --- /dev/null +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressEvents.kt @@ -0,0 +1,14 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.createroom.impl.joinbyaddress + +sealed interface JoinRoomByAddressEvents { + data object Dismiss : JoinRoomByAddressEvents + data object Continue : JoinRoomByAddressEvents + data class UpdateAddress(val address: String) : JoinRoomByAddressEvents +} diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressNode.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressNode.kt new file mode 100644 index 0000000000..d6338ab43c --- /dev/null +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressNode.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.createroom.impl.joinbyaddress + +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 com.bumble.appyx.core.plugin.plugins +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.createroom.CreateRoomNavigator +import io.element.android.libraries.di.SessionScope + +@ContributesNode(SessionScope::class) +class JoinRoomByAddressNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + presenterFactory: JoinRoomByAddressPresenter.Factory, +) : Node(buildContext, plugins = plugins) { + private val navigator = plugins().first() + private val presenter = presenterFactory.create(navigator) + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + JoinRoomByAddressView( + state = state, + modifier = modifier + ) + } +} diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressPresenter.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressPresenter.kt new file mode 100644 index 0000000000..cf4788cbbf --- /dev/null +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressPresenter.kt @@ -0,0 +1,138 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.createroom.impl.joinbyaddress + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.setValue +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import io.element.android.features.createroom.CreateRoomNavigator +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.core.data.tryOrNull +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.RoomAlias +import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias +import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper +import kotlinx.coroutines.delay +import kotlinx.coroutines.withTimeoutOrNull +import kotlin.time.Duration.Companion.seconds + +private const val ADDRESS_RESOLVE_TIMEOUT_IN_SECONDS = 10 + +class JoinRoomByAddressPresenter @AssistedInject constructor( + @Assisted private val navigator: CreateRoomNavigator, + private val client: MatrixClient, + private val roomAliasHelper: RoomAliasHelper, +) : Presenter { + @AssistedFactory + interface Factory { + fun create(navigator: CreateRoomNavigator): JoinRoomByAddressPresenter + } + + @Composable + override fun present(): JoinRoomByAddressState { + var address by remember { mutableStateOf("") } + var internalAddressState by remember { mutableStateOf(RoomAddressState.Unknown) } + var validateAddress: Boolean by remember { mutableStateOf(false) } + + fun handleEvents(event: JoinRoomByAddressEvents) { + when (event) { + JoinRoomByAddressEvents.Continue -> { + when (val currentState = internalAddressState) { + is RoomAddressState.RoomFound -> onRoomFound(currentState) + else -> validateAddress = true + } + } + JoinRoomByAddressEvents.Dismiss -> navigator.onDismissJoinRoomByAddress() + is JoinRoomByAddressEvents.UpdateAddress -> { + validateAddress = false + address = event.address.trim() + } + } + } + + RoomAddressStateEffect( + fullAddress = address, + onRoomAddressStateChange = { addressState -> + internalAddressState = addressState + if (addressState is RoomAddressState.RoomFound && validateAddress) { + onRoomFound(addressState) + } + } + ) + + val addressState by remember { + derivedStateOf { + // We only want to show the "RoomFound" state as long as the user didn't validate the address. + if (validateAddress || internalAddressState is RoomAddressState.RoomFound) { + internalAddressState + } else { + RoomAddressState.Unknown + } + } + } + + return JoinRoomByAddressState( + address = address, + addressState = addressState, + eventSink = ::handleEvents + ) + } + + private fun onRoomFound(state: RoomAddressState.RoomFound) { + navigator.onDismissJoinRoomByAddress() + navigator.onOpenRoom( + roomIdOrAlias = state.resolved.roomId.toRoomIdOrAlias(), + serverNames = state.resolved.servers + ) + } + + @Composable + private fun RoomAddressStateEffect( + fullAddress: String, + onRoomAddressStateChange: (RoomAddressState) -> Unit, + ) { + val onChange by rememberUpdatedState(onRoomAddressStateChange) + LaunchedEffect(fullAddress) { + // Whenever the address changes, reset the state to unknown + onChange(RoomAddressState.Unknown) + // debounce the room address resolution + delay(300) + val roomAlias = tryOrNull { RoomAlias(fullAddress) } + if (roomAlias != null && roomAliasHelper.isRoomAliasValid(roomAlias)) { + onChange(RoomAddressState.Resolving) + onChange(client.resolveRoomAddress(roomAlias)) + } else { + onChange(RoomAddressState.Invalid) + } + } + } + + private suspend fun MatrixClient.resolveRoomAddress(roomAlias: RoomAlias): RoomAddressState { + return withTimeoutOrNull(ADDRESS_RESOLVE_TIMEOUT_IN_SECONDS.seconds) { + resolveRoomAlias(roomAlias) + .fold( + onSuccess = { resolved -> + if (resolved.isPresent) { + RoomAddressState.RoomFound(resolved.get()) + } else { + RoomAddressState.RoomNotFound + } + }, + onFailure = { _ -> RoomAddressState.RoomNotFound } + ) + } ?: RoomAddressState.RoomNotFound + } +} diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressState.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressState.kt new file mode 100644 index 0000000000..11791181e1 --- /dev/null +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressState.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.createroom.impl.joinbyaddress + +import androidx.compose.runtime.Immutable +import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias + +data class JoinRoomByAddressState( + val address: String, + val addressState: RoomAddressState, + val eventSink: (JoinRoomByAddressEvents) -> Unit +) + +@Immutable +sealed interface RoomAddressState { + data object Unknown : RoomAddressState + data object Invalid : RoomAddressState + data object Resolving : RoomAddressState + data object RoomNotFound : RoomAddressState + data class RoomFound(val resolved: ResolvedRoomAlias) : RoomAddressState +} diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressStateProvider.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressStateProvider.kt new file mode 100644 index 0000000000..6281a8e8e3 --- /dev/null +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressStateProvider.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.createroom.impl.joinbyaddress + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias + +open class JoinRoomByAddressStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aJoinRoomByAddressState(), + aJoinRoomByAddressState(address = "#room-"), + aJoinRoomByAddressState(address = "#room-", addressState = RoomAddressState.Invalid), + aJoinRoomByAddressState(address = "#room-name:matrix.org", addressState = RoomAddressState.Resolving), + aJoinRoomByAddressState(address = "#room-name-none:matrix.org", addressState = RoomAddressState.RoomNotFound), + aJoinRoomByAddressState( + address = "#room-name:matrix.org", + addressState = RoomAddressState.RoomFound(ResolvedRoomAlias(RoomId("!aRoom:id"), emptyList())), + ), + ) +} + +fun aJoinRoomByAddressState( + address: String = "", + addressState: RoomAddressState = RoomAddressState.Unknown, + eventSink: (JoinRoomByAddressEvents) -> Unit = {}, +) = JoinRoomByAddressState( + address = address, + addressState = addressState, + eventSink = eventSink +) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressView.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressView.kt new file mode 100644 index 0000000000..c256f5140c --- /dev/null +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressView.kt @@ -0,0 +1,134 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.createroom.impl.joinbyaddress + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardCapitalization +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import io.element.android.features.createroom.impl.R +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Button +import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet +import io.element.android.libraries.designsystem.theme.components.TextField +import io.element.android.libraries.designsystem.theme.components.TextFieldValidity +import io.element.android.libraries.ui.strings.CommonStrings + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun JoinRoomByAddressView( + state: JoinRoomByAddressState, + modifier: Modifier = Modifier, +) { + val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + ModalBottomSheet( + modifier = modifier, + sheetState = sheetState, + onDismissRequest = { + state.eventSink(JoinRoomByAddressEvents.Dismiss) + }, + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(all = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + RoomAddressField( + address = state.address, + addressState = state.addressState, + requestFocus = sheetState.isVisible, + onAddressChange = { + state.eventSink(JoinRoomByAddressEvents.UpdateAddress(it)) + }, + onContinue = { + state.eventSink(JoinRoomByAddressEvents.Continue) + }, + ) + Spacer(modifier = Modifier.height(24.dp)) + Button( + text = stringResource(CommonStrings.action_continue), + modifier = Modifier.fillMaxWidth(), + showProgress = state.addressState is RoomAddressState.Resolving, + onClick = { + state.eventSink(JoinRoomByAddressEvents.Continue) + } + ) + } + } +} + +@Composable +private fun RoomAddressField( + address: String, + addressState: RoomAddressState, + requestFocus: Boolean, + onAddressChange: (String) -> Unit, + onContinue: () -> Unit, + modifier: Modifier = Modifier, +) { + val focusRequester = remember { FocusRequester() } + if (requestFocus) { + LaunchedEffect(Unit) { focusRequester.requestFocus() } + } + TextField( + modifier = modifier.focusRequester(focusRequester), + value = address, + label = stringResource(R.string.screen_start_chat_join_room_by_address_action), + placeholder = stringResource(R.string.screen_start_chat_join_room_by_address_placeholder), + supportingText = when (addressState) { + RoomAddressState.Invalid -> stringResource(R.string.screen_start_chat_join_room_by_address_invalid_address) + is RoomAddressState.RoomFound -> stringResource(R.string.screen_start_chat_join_room_by_address_room_found) + RoomAddressState.RoomNotFound -> stringResource(R.string.screen_start_chat_join_room_by_address_room_not_found) + RoomAddressState.Unknown, RoomAddressState.Resolving -> stringResource(R.string.screen_start_chat_join_room_by_address_supporting_text) + }, + validity = when (addressState) { + RoomAddressState.Unknown, RoomAddressState.Resolving -> TextFieldValidity.None + RoomAddressState.Invalid, RoomAddressState.RoomNotFound -> TextFieldValidity.Invalid + is RoomAddressState.RoomFound -> TextFieldValidity.Valid + }, + onValueChange = onAddressChange, + singleLine = true, + keyboardOptions = KeyboardOptions( + capitalization = KeyboardCapitalization.None, + autoCorrectEnabled = false, + keyboardType = KeyboardType.Uri, + imeAction = ImeAction.Go + ), + keyboardActions = KeyboardActions( + onGo = { onContinue() } + ) + ) +} + +@PreviewsDayNight +@Composable +internal fun JoinRoomByAddressViewPreview( + @PreviewParameter(JoinRoomByAddressStateProvider::class) state: JoinRoomByAddressState +) = ElementPreview { + JoinRoomByAddressView(state = state) +} diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootNode.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootNode.kt index f3a836f441..8ee982906c 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootNode.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootNode.kt @@ -20,9 +20,10 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import im.vector.app.features.analytics.plan.MobileScreen import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.createroom.CreateRoomNavigator import io.element.android.libraries.deeplink.usecase.InviteFriendsUseCase import io.element.android.libraries.di.SessionScope -import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias import io.element.android.services.analytics.api.AnalyticsService @ContributesNode(SessionScope::class) @@ -33,18 +34,7 @@ class CreateRoomRootNode @AssistedInject constructor( private val analyticsService: AnalyticsService, private val inviteFriendsUseCase: InviteFriendsUseCase, ) : Node(buildContext, plugins = plugins) { - interface Callback : Plugin { - fun onCreateNewRoom() - fun onStartChatSuccess(roomId: RoomId) - } - - private fun onCreateNewRoom() { - plugins().forEach { it.onCreateNewRoom() } - } - - private fun onStartChatSuccess(roomId: RoomId) { - plugins().forEach { it.onStartChatSuccess(roomId) } - } + private val navigator = plugins().first() init { lifecycle.subscribe( @@ -60,8 +50,11 @@ class CreateRoomRootNode @AssistedInject constructor( state = state, modifier = modifier, onCloseClick = this::navigateUp, - onNewRoomClick = ::onCreateNewRoom, - onOpenDM = ::onStartChatSuccess, + onNewRoomClick = navigator::onCreateNewRoom, + onOpenDM = { + navigator.onOpenRoom(roomIdOrAlias = it.toRoomIdOrAlias(), serverNames = emptyList()) + }, + onJoinByAddressClick = navigator::onShowJoinRoomByAddress, onInviteFriendsClick = { invitePeople(activity) } ) } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt index 7eb2f49511..6f008d7283 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt @@ -55,6 +55,7 @@ fun CreateRoomRootView( onNewRoomClick: () -> Unit, onOpenDM: (RoomId) -> Unit, onInviteFriendsClick: () -> Unit, + onJoinByAddressClick: () -> Unit, modifier: Modifier = Modifier, ) { Scaffold( @@ -89,6 +90,7 @@ fun CreateRoomRootView( state = state, onNewRoomClick = onNewRoomClick, onInvitePeopleClick = onInviteFriendsClick, + onJoinByAddressClick = onJoinByAddressClick, onDmClick = onOpenDM, ) } @@ -153,6 +155,7 @@ private fun CreateRoomActionButtonsList( state: CreateRoomRootState, onNewRoomClick: () -> Unit, onInvitePeopleClick: () -> Unit, + onJoinByAddressClick: () -> Unit, onDmClick: (RoomId) -> Unit, ) { LazyColumn { @@ -170,6 +173,13 @@ private fun CreateRoomActionButtonsList( onClick = onInvitePeopleClick, ) } + item { + CreateRoomActionButton( + iconRes = CompoundDrawables.ic_compound_room, + text = stringResource(R.string.screen_start_chat_join_room_by_address_action), + onClick = onJoinByAddressClick, + ) + } if (state.userListState.recentDirectRooms.isNotEmpty()) { item { ListSectionHeader( @@ -230,6 +240,7 @@ internal fun CreateRoomRootViewPreview(@PreviewParameter(CreateRoomRootStateProv onCloseClick = {}, onNewRoomClick = {}, onOpenDM = {}, + onJoinByAddressClick = {}, onInviteFriendsClick = {}, ) } diff --git a/features/createroom/impl/src/main/res/values/localazy.xml b/features/createroom/impl/src/main/res/values/localazy.xml index 6ed5510ce0..71208bf9e9 100644 --- a/features/createroom/impl/src/main/res/values/localazy.xml +++ b/features/createroom/impl/src/main/res/values/localazy.xml @@ -20,4 +20,10 @@ You can change this anytime in room settings." "Create a room" "Topic (optional)" "An error occurred when trying to start a chat" + "Join room by address" + "Not a valid address" + "Enter…" + "Matching room found" + "Room not found" + "e.g. #room-name:matrix.org" diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/FakeCreateRoomNavigator.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/FakeCreateRoomNavigator.kt new file mode 100644 index 0000000000..b173865b61 --- /dev/null +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/FakeCreateRoomNavigator.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.createroom.impl + +import io.element.android.features.createroom.CreateRoomNavigator +import io.element.android.libraries.matrix.api.core.RoomIdOrAlias + +class FakeCreateRoomNavigator( + private val openRoomLambda: (roomIdOrAlias: RoomIdOrAlias, serverNames: List) -> Unit = { _, _ -> }, + private val createNewRoomLambda: () -> Unit = {}, + private val showJoinRoomByAddressLambda: () -> Unit = {}, + private val dismissJoinRoomByAddressLambda: () -> Unit = {}, + ) : CreateRoomNavigator { + override fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List) { + openRoomLambda(roomIdOrAlias, serverNames) + } + + override fun onCreateNewRoom() { + createNewRoomLambda() + } + + override fun onShowJoinRoomByAddress() { + showJoinRoomByAddressLambda() + } + + override fun onDismissJoinRoomByAddress() { + dismissJoinRoomByAddressLambda() + } +} diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressPresenterTest.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressPresenterTest.kt new file mode 100644 index 0000000000..d1ee3e801c --- /dev/null +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressPresenterTest.kt @@ -0,0 +1,140 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.createroom.impl.joinbyaddress + +import com.google.common.truth.Truth.assertThat +import io.element.android.features.createroom.CreateRoomNavigator +import io.element.android.features.createroom.impl.FakeCreateRoomNavigator +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.RoomIdOrAlias +import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper +import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.matrix.test.room.alias.FakeRoomAliasHelper +import io.element.android.tests.testutils.lambda.assert +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.test +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class JoinRoomByAddressPresenterTest { + @Test + fun `present - initial state`() = runTest { + val presenter = createJoinRoomByAddressPresenter() + presenter.test { + with(awaitItem()) { + assertThat(address).isEmpty() + assertThat(addressState).isEqualTo(RoomAddressState.Unknown) + } + } + } + + @Test + fun `present - invalid address`() = runTest { + val presenter = createJoinRoomByAddressPresenter( + roomAliasHelper = FakeRoomAliasHelper( + isRoomAliasValidLambda = { false } + ) + ) + presenter.test { + with(awaitItem()) { + eventSink(JoinRoomByAddressEvents.UpdateAddress("invalid_address")) + } + with(awaitItem()) { + assertThat(address).isEqualTo("invalid_address") + assertThat(addressState).isEqualTo(RoomAddressState.Unknown) + eventSink(JoinRoomByAddressEvents.Continue) + } + // The address should be marked as invalid only after the user tries to continue + with(awaitItem()) { + assertThat(address).isEqualTo("invalid_address") + assertThat(addressState).isEqualTo(RoomAddressState.Invalid) + } + } + } + + @Test + fun `present - room found`() = runTest { + val openRoomLambda = lambdaRecorder, Unit> { _, _ -> } + val dismissJoinRoomByAddressLambda = lambdaRecorder { } + val navigator = FakeCreateRoomNavigator( + openRoomLambda = openRoomLambda, + dismissJoinRoomByAddressLambda = dismissJoinRoomByAddressLambda + ) + val presenter = createJoinRoomByAddressPresenter(navigator = navigator) + presenter.test { + with(awaitItem()) { + eventSink(JoinRoomByAddressEvents.UpdateAddress("#room_found:matrix.org")) + } + with(awaitItem()) { + assertThat(address).isEqualTo("#room_found:matrix.org") + assertThat(addressState).isEqualTo(RoomAddressState.Unknown) + } + with(awaitItem()) { + assertThat(address).isEqualTo("#room_found:matrix.org") + assertThat(addressState).isInstanceOf(RoomAddressState.RoomFound::class.java) + eventSink(JoinRoomByAddressEvents.Continue) + } + assert(openRoomLambda).isCalledOnce() + assert(dismissJoinRoomByAddressLambda).isCalledOnce() + } + } + + @Test + fun `present - room not found`() = runTest { + val presenter = createJoinRoomByAddressPresenter( + matrixClient = FakeMatrixClient( + resolveRoomAliasResult = { Result.failure(RuntimeException()) } + ) + ) + presenter.test { + with(awaitItem()) { + eventSink(JoinRoomByAddressEvents.UpdateAddress("#room_not_found:matrix.org")) + } + with(awaitItem()) { + assertThat(address).isEqualTo("#room_not_found:matrix.org") + assertThat(addressState).isEqualTo(RoomAddressState.Unknown) + eventSink(JoinRoomByAddressEvents.Continue) + } + with(awaitItem()) { + assertThat(address).isEqualTo("#room_not_found:matrix.org") + assertThat(addressState).isEqualTo(RoomAddressState.Resolving) + } + with(awaitItem()) { + assertThat(address).isEqualTo("#room_not_found:matrix.org") + assertThat(addressState).isEqualTo(RoomAddressState.RoomNotFound) + } + } + } + + @Test + fun `present - dismiss`() = runTest { + val dismissJoinRoomByAddressLambda = lambdaRecorder { } + val navigator = FakeCreateRoomNavigator( + dismissJoinRoomByAddressLambda = dismissJoinRoomByAddressLambda + ) + val presenter = createJoinRoomByAddressPresenter(navigator = navigator) + presenter.test { + with(awaitItem()) { + eventSink(JoinRoomByAddressEvents.Dismiss) + } + assert(dismissJoinRoomByAddressLambda).isCalledOnce() + } + } + + private fun createJoinRoomByAddressPresenter( + navigator: CreateRoomNavigator = FakeCreateRoomNavigator(), + matrixClient: MatrixClient = FakeMatrixClient(), + roomAliasHelper: RoomAliasHelper = FakeRoomAliasHelper(), + ): JoinRoomByAddressPresenter { + return JoinRoomByAddressPresenter( + navigator = navigator, + client = matrixClient, + roomAliasHelper = roomAliasHelper, + ) + } +} diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressViewTest.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressViewTest.kt new file mode 100644 index 0000000000..bb3ee3e493 --- /dev/null +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressViewTest.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.createroom.impl.joinbyaddress + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performTextInput +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.features.createroom.impl.R +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.clickOn +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class JoinRoomByAddressViewTest { + @get:Rule + val rule = createAndroidComposeRule() + + @Test + fun `entering text emits the expected event`() { + val eventsRecorder = EventsRecorder() + rule.setJoinRoomByAddressView( + aJoinRoomByAddressState( + eventSink = eventsRecorder, + ) + ) + val text = rule.activity.getString(R.string.screen_start_chat_join_room_by_address_action) + rule.onNodeWithText(text).performTextInput("#address:matrix.org") + eventsRecorder.assertSingle(JoinRoomByAddressEvents.UpdateAddress("#address:matrix.org")) + } + + @Test + fun `clicking on continue emits the expected event`() { + val eventsRecorder = EventsRecorder() + rule.setJoinRoomByAddressView( + aJoinRoomByAddressState( + eventSink = eventsRecorder, + ) + ) + rule.clickOn(CommonStrings.action_continue) + eventsRecorder.assertSingle(JoinRoomByAddressEvents.Continue) + } +} + +private fun AndroidComposeTestRule.setJoinRoomByAddressView( + state: JoinRoomByAddressState, +) { + setContent { + JoinRoomByAddressView(state = state) + } +} diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootViewTest.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootViewTest.kt index 88390f3b8c..e446a13417 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootViewTest.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootViewTest.kt @@ -101,6 +101,21 @@ class CreateRoomRootViewTest { rule.onNodeWithText(firstRoom.matrixUser.getBestName()).performClick() } } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on Join room by address invokes the expected callback`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnce { + rule.setCreateRoomRootView( + aCreateRoomRootState( + eventSink = eventsRecorder, + ), + onJoinRoomByAddressClick = it + ) + rule.clickOn(R.string.screen_start_chat_join_room_by_address_action) + } + } } private fun AndroidComposeTestRule.setCreateRoomRootView( @@ -109,6 +124,7 @@ private fun AndroidComposeTestRule.setCreat onNewRoomClick: () -> Unit = EnsureNeverCalled(), onOpenDM: (RoomId) -> Unit = EnsureNeverCalledWithParam(), onInviteFriendsClick: () -> Unit = EnsureNeverCalled(), + onJoinRoomByAddressClick: () -> Unit = EnsureNeverCalled(), ) { setContent { CreateRoomRootView( @@ -117,6 +133,7 @@ private fun AndroidComposeTestRule.setCreat onNewRoomClick = onNewRoomClick, onOpenDM = onOpenDM, onInviteFriendsClick = onInviteFriendsClick, + onJoinByAddressClick = onJoinRoomByAddressClick ) } } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt index fbf91fa46d..2272e8fe94 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt @@ -45,6 +45,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.debugPlaceholderBackground import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.TextField +import io.element.android.libraries.designsystem.theme.components.TextFieldValidity import io.element.android.libraries.ui.strings.CommonStrings @Composable @@ -90,7 +91,7 @@ fun BugReportView( keyboardController?.hide() }), minLines = 3, - isError = state.isDescriptionInError, + validity = if (state.isDescriptionInError) TextFieldValidity.Invalid else TextFieldValidity.None, ) } Spacer(modifier = Modifier.height(16.dp)) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt index 45fa5113e5..877442ef1b 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt @@ -152,8 +152,7 @@ private fun RoomListScaffold( onClick = onCreateRoomClick ) { Icon( - // Note cannot use Icons.Outlined.EditSquare, it does not exist :/ - imageVector = CompoundIcons.Compose(), + imageVector = CompoundIcons.Plus(), contentDescription = stringResource(id = R.string.screen_roomlist_a11y_create_message), tint = ElementTheme.colors.iconOnSolidPrimary, ) diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordView.kt index 2c118c3049..998b46046c 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordView.kt @@ -33,6 +33,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.TextField +import io.element.android.libraries.designsystem.theme.components.TextFieldValidity import io.element.android.libraries.ui.strings.CommonStrings @Composable @@ -99,7 +100,7 @@ private fun Content(text: String, onTextChange: (String) -> Unit, hasError: Bool Icon(imageVector = image, description) } }, - isError = hasError, + validity = if (hasError) TextFieldValidity.Invalid else TextFieldValidity.None, supportingText = if (hasError) { stringResource(R.string.screen_reset_encryption_password_error) } else { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TextField.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TextField.kt index 333716e6c2..51041f8168 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TextField.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TextField.kt @@ -58,7 +58,7 @@ fun TextField( placeholder: String? = null, leadingIcon: @Composable (() -> Unit)? = null, trailingIcon: @Composable (() -> Unit)? = null, - isError: Boolean = false, + validity: TextFieldValidity = TextFieldValidity.None, enabled: Boolean = true, readOnly: Boolean = false, singleLine: Boolean = false, @@ -93,7 +93,7 @@ fun TextField( readOnly = readOnly, enabled = enabled, isFocused = isFocused, - isError = isError, + validity = validity, leadingIcon = leadingIcon, placeholder = placeholder, isTextEmpty = value.isEmpty(), @@ -114,7 +114,7 @@ fun TextField( placeholder: String? = null, leadingIcon: @Composable (() -> Unit)? = null, trailingIcon: @Composable (() -> Unit)? = null, - isError: Boolean = false, + validity: TextFieldValidity? = null, enabled: Boolean = true, readOnly: Boolean = false, singleLine: Boolean = false, @@ -149,7 +149,7 @@ fun TextField( readOnly = readOnly, enabled = enabled, isFocused = isFocused, - isError = isError, + validity = validity, leadingIcon = leadingIcon, placeholder = placeholder, isTextEmpty = value.text.isEmpty(), @@ -166,7 +166,7 @@ private fun DecorationBox( enabled: Boolean, readOnly: Boolean, isFocused: Boolean, - isError: Boolean, + validity: TextFieldValidity?, placeholder: String?, isTextEmpty: Boolean, supportingText: String?, @@ -187,7 +187,7 @@ private fun DecorationBox( enabled = enabled, readOnly = readOnly, isFocused = isFocused, - isError = isError + isError = validity == TextFieldValidity.Invalid ) { Row(modifier = Modifier.padding(16.dp)) { if (leadingIcon != null) { @@ -216,7 +216,7 @@ private fun DecorationBox( } if (supportingText != null) { Spacer(modifier = Modifier.height(4.dp)) - SupportingTextLayout(isError, supportingText) + SupportingTextLayout(validity, supportingText) } } } @@ -254,24 +254,45 @@ private fun TextFieldContainer( } @Composable -private fun SupportingTextLayout(isError: Boolean, supportingText: String) { +private fun SupportingTextLayout(validity: TextFieldValidity?, supportingText: String) { Row(horizontalArrangement = spacedBy(4.dp)) { - if (isError) { - Icon( - imageVector = CompoundIcons.Error(), - contentDescription = null, - modifier = Modifier.size(16.dp), - tint = ElementTheme.colors.iconCriticalPrimary - ) + when (validity) { + TextFieldValidity.Invalid -> { + Icon( + imageVector = CompoundIcons.Error(), + contentDescription = null, + modifier = Modifier.size(16.dp), + tint = ElementTheme.colors.iconCriticalPrimary + ) + } + TextFieldValidity.Valid -> { + Icon( + imageVector = CompoundIcons.CheckCircleSolid(), + contentDescription = null, + modifier = Modifier.size(16.dp), + tint = ElementTheme.colors.iconSuccessPrimary + ) + } + else -> Unit } Text( text = supportingText, - color = if (isError) ElementTheme.colors.textCriticalPrimary else ElementTheme.colors.textSecondary, + color = when (validity) { + TextFieldValidity.Invalid -> ElementTheme.colors.textCriticalPrimary + TextFieldValidity.Valid -> ElementTheme.colors.textSuccessPrimary + else -> ElementTheme.colors.textSecondary + }, style = ElementTheme.typography.fontBodySmRegular, ) } } +enum class TextFieldValidity { + None, + Invalid, + Valid +} + @Composable private fun textFieldStyle(enabled: Boolean): TextStyle { return ElementTheme.typography.fontBodyLgRegular.copy( @@ -283,11 +304,11 @@ private fun textFieldStyle(enabled: Boolean): TextStyle { ) } -@Preview(group = PreviewGroup.TextFields) +@Preview(group = PreviewGroup.TextFields, heightDp = 1000) @Composable internal fun TextFieldsLightPreview() = ElementPreviewLight { ContentToPreview() } -@Preview(group = PreviewGroup.TextFields) +@Preview(group = PreviewGroup.TextFields, heightDp = 1000) @Composable internal fun TextFieldsDarkPreview() = ElementPreviewDark { ContentToPreview() } @@ -295,15 +316,15 @@ internal fun TextFieldsDarkPreview() = ElementPreviewDark { ContentToPreview() } @ExcludeFromCoverage private fun ContentToPreview() { Column(modifier = Modifier.padding(4.dp)) { - allBooleans.forEach { isError -> + TextFieldValidity.entries.forEach { validity -> allBooleans.forEach { enabled -> allBooleans.forEach { readonly -> TextField( onValueChange = {}, label = "Label", - value = "Hello er=${isError.asInt()}, en=${enabled.asInt()}, ro=${readonly.asInt()}", + value = "Hello val=$validity, en=${enabled.asInt()}, ro=${readonly.asInt()}", supportingText = "Supporting text", - isError = isError, + validity = validity, enabled = enabled, readOnly = readonly, ) diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressField.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressField.kt index acd7723b87..f97e2ff766 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressField.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressField.kt @@ -15,6 +15,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TextField +import io.element.android.libraries.designsystem.theme.components.TextFieldValidity import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.testTag import io.element.android.libraries.ui.strings.CommonStrings @@ -56,7 +57,10 @@ fun RoomAddressField( } else -> supportingText }, - isError = addressValidity.isError(), + validity = when (addressValidity) { + RoomAddressValidity.InvalidSymbols, RoomAddressValidity.NotAvailable -> TextFieldValidity.Invalid + else -> TextFieldValidity.None + }, onValueChange = onAddressChange, singleLine = true, ) diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressValidity.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressValidity.kt index 25af0ab98d..fef36ccd20 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressValidity.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressValidity.kt @@ -19,8 +19,4 @@ sealed interface 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/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_0_en.png new file mode 100644 index 0000000000..b46d76faa6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ddac666d01929c8df834cff1e932eba12dbf377c7954cd4fdf2fa13bbb1e77be +size 16758 diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_1_en.png new file mode 100644 index 0000000000..e73def12a8 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae779c152a099185e14a4301ff47825cfd809a81e651e36cb3261fb3371f61e7 +size 17424 diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_2_en.png new file mode 100644 index 0000000000..e9b5bde96e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3c89ad3af16b43b6798c30077651b7c9eefd0059d847f5ff643877a2d3d3403 +size 17025 diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_3_en.png new file mode 100644 index 0000000000..bca0bfcc78 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f39dc9170e9e85d1f56b8b7f1b80fbbb7d5acaa762a585a6c52b494c8b25b285 +size 20884 diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_4_en.png new file mode 100644 index 0000000000..3731d3e265 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:30c89188415418f6015349fe9371792052f8c171272ce7ccb45352e79b15774f +size 20192 diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_5_en.png new file mode 100644 index 0000000000..dc23bff507 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Day_5_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a67b688327c3507e0072fad948b62c19acfb454aa05ac42832ee1a980c725492 +size 20174 diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_0_en.png new file mode 100644 index 0000000000..e5781c467d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f4285614e42cec849331dfac0a8d67786cc95f768815d5818a1c06fb97f14530 +size 15599 diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_1_en.png new file mode 100644 index 0000000000..6e2fccda25 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9610111f2ad914faf1f890faa1b593ccf04ec42248f04586f403a0f8bfbd5991 +size 16196 diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_2_en.png new file mode 100644 index 0000000000..a6a270af66 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9be8c0f212d2ba8d1e4746038280114f50367f078e5ef63ae4e5faa730552630 +size 15629 diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_3_en.png new file mode 100644 index 0000000000..c59ae8781a --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca43cfd340541ee672c5b79727f4033fa21a4e5351dbef55cf48999946d3a2c6 +size 19474 diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_4_en.png new file mode 100644 index 0000000000..e3d91770a6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e04c7204d560370bcd2ed9bf775a892b65f22bd395f7079375e963cd46cce8ec +size 18710 diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_5_en.png new file mode 100644 index 0000000000..f10cf4b795 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.joinbyaddress_JoinRoomByAddressView_Night_5_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9824bb57bbe04b48d5a2750a56a9cd36cd32bf4a4a2ebf9dd19c213e1cfb9d6 +size 18565 diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.root_CreateRoomRootView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.root_CreateRoomRootView_Day_0_en.png index ec1c92eb4e..a5a5d5f71e 100644 --- a/tests/uitests/src/test/snapshots/images/features.createroom.impl.root_CreateRoomRootView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.root_CreateRoomRootView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c6c1aed2fb05e5220d335bc0ffc9a6851e9207efd2dcabb2ce2100e61516bc57 -size 21409 +oid sha256:f19636ec23d81de10c454b3a0f4c957b999b0fd706363854eb81b62d3072ebcd +size 25781 diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.root_CreateRoomRootView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.root_CreateRoomRootView_Day_3_en.png index 9c0d5f7a9a..413ee4d711 100644 --- a/tests/uitests/src/test/snapshots/images/features.createroom.impl.root_CreateRoomRootView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.root_CreateRoomRootView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:35e14513f620f9060ed5b7d8db55175f96d57b45f4187b4f9e2be4bb56016dcd -size 48218 +oid sha256:e86339de00f7e6d44c1d36004c79a9c077d7d99d55ba01da8ea9e4d3b4567367 +size 53085 diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.root_CreateRoomRootView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.root_CreateRoomRootView_Night_0_en.png index f2582e324c..78416bc049 100644 --- a/tests/uitests/src/test/snapshots/images/features.createroom.impl.root_CreateRoomRootView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.root_CreateRoomRootView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d2d669d6ce18e6cf641b85f7edf1870b96f3289d55dd89779e1c6388147b4055 -size 20409 +oid sha256:c9f8d6270b9db5463649ce357463cec93945d924bebaeb7da14bfa2936088620 +size 24735 diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.root_CreateRoomRootView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.root_CreateRoomRootView_Night_3_en.png index 89544d8cec..77ff9cc7cd 100644 --- a/tests/uitests/src/test/snapshots/images/features.createroom.impl.root_CreateRoomRootView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.root_CreateRoomRootView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7976aeaf533239d0bda8204e305d1ef6b977b0f7f8755025c36fe76b1a36e8c4 -size 48097 +oid sha256:baa7c2ce997853e150eeb0193798af8d2ec1084b1791da579b2de6544e07d06f +size 52657 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_0_en.png index 845f5acd9e..ccdeb22980 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:09b8dcff40d9231cbd5b81db4836081046dba2bab37d9b4e7bae49abf7debd22 -size 82933 +oid sha256:5b120ebcbb9dae050b83d6d769918e2cb2a0088ea9670df9d15adc967ca69bbd +size 82399 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_10_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_10_en.png index e7e953fd68..af8ab93af2 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:edb52d5fc252ee719f6aec12faf98b8976eb9a61992dba510c06386999975ed2 -size 105988 +oid sha256:aba3ee6d75f3b8671e4249e98386a689f59a8635bc43693c8333ea794b368726 +size 105477 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_1_en.png index 845f5acd9e..ccdeb22980 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:09b8dcff40d9231cbd5b81db4836081046dba2bab37d9b4e7bae49abf7debd22 -size 82933 +oid sha256:5b120ebcbb9dae050b83d6d769918e2cb2a0088ea9670df9d15adc967ca69bbd +size 82399 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_2_en.png index d165a8c1f7..73e1e61ee7 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e398afce8d17e333d7f33ed134dbc1de6651e525f72dff0980827df2609b5273 -size 83578 +oid sha256:fff21c904f2fdfb25a39a931fc53909c35555247128959bd4cdd0c839b70c105 +size 83039 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_6_en.png index 96cd13c035..4f7d62b822 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:85925c39a59d3ceca4da5234084a3218a33670a9e3fbd3121ef601e06fb93297 -size 100367 +oid sha256:58178368a0a5de71b236c9f0f82457f0cc1a94ae0f828ba72c0851bbce0a29e9 +size 99858 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_7_en.png index 95582e918b..a8880ed6cb 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0762f1f181107fad31c233bb1013cf82c6de2b94500e592609fdcc43e53a5807 -size 46550 +oid sha256:9988051950123717fc5e2e6b6db2be0b905c6d47e81a485a20cd23daab9fbba1 +size 46019 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_8_en.png index a22032f937..6e0088ee14 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Day_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3fef8280fdce4608a30e24063699ab99bcb55865b2df51b070e2dc0eafce23a6 -size 41271 +oid sha256:f65131302fab3b0ba710528947376b57230a74731aa6549473998c30f4e07f6e +size 40681 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_0_en.png index 5dc5152a67..7a84b4ad13 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dc64620fffb86bb5bcbb175795ebde2b84c18e6989f99528046b1ab14f3c9005 -size 89396 +oid sha256:56ec28811fc81c49f1f3fa5c9125d668b0401ee4645feb33508ba32a33a6bf55 +size 88850 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_10_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_10_en.png index 3bb6183058..c355721e47 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1ee1b7d094db4cd00af34d1ef79c6e71ed61cec585612c538a0bc870c64b9966 -size 111814 +oid sha256:3125f559a0531c2b3b63957a7ca7cff2a718c5a2e00f5213e38e6e65b005f942 +size 111313 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_1_en.png index 5dc5152a67..7a84b4ad13 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dc64620fffb86bb5bcbb175795ebde2b84c18e6989f99528046b1ab14f3c9005 -size 89396 +oid sha256:56ec28811fc81c49f1f3fa5c9125d668b0401ee4645feb33508ba32a33a6bf55 +size 88850 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_2_en.png index defce1a22e..c6ad887622 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:28ba0acc4ef26efdb4bc2f18c1643a82a180cfa429302da9df3ebce73f3f6552 -size 89287 +oid sha256:961ea9cb682807f6a423cb3fa69f3d7a8838339fdb33de8de8baf55ac22ec239 +size 88758 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_6_en.png index 3543bb4fb2..ecc286dbd9 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:49097acd61396b5190aa1820c61d826b04738dd56acb4cdfb5df5b04c486e961 -size 106227 +oid sha256:3cdebb7664e5a7aefbdc35f8d442cc39a07f5debc3fc9ceb7be384eefdf3da33 +size 105760 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_7_en.png index 6ab0b8da39..7552177fc0 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2e339f50db6e8b06bdf5a26559827f92f5a9fd2f20bce4a45d4edf5d45e071b9 -size 53790 +oid sha256:2264d2aa03b36178a6ffa315a1147dba5cd6a698ae2fb4fe0942857964de2b47 +size 53250 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_8_en.png index fefb0a3731..66309e8b71 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl_RoomListView_Night_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9496a4dd417cb64c20a1d53fb27f9f567f0eefc8c0ad428a9c6c4041f66eddb0 -size 47845 +oid sha256:1c6dc3c0c72d1ea8a980f393d7cfe2c9e0392016c8fc25b901817dd077d8526c +size 47265 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_TextFieldsDark_TextFields_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_TextFieldsDark_TextFields_en.png index f0177c8a61..6302c4f1df 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_TextFieldsDark_TextFields_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_TextFieldsDark_TextFields_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:165bbcda11115c490250a86ac45d84277362a8c0fc0931473f1dc729b5eaa70d -size 42804 +oid sha256:0eb5d9fc0da36b69863c9ab9ebac6ef2cfe5d57c0f161be2ebc6247229819d17 +size 43209 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_TextFieldsLight_TextFields_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_TextFieldsLight_TextFields_en.png index 806bbe951c..24d888598d 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_TextFieldsLight_TextFields_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_TextFieldsLight_TextFields_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:86b121d1b8c7ea4b2401ca6ea36897b630f41b5fa9961c5bcb8746a142b8c26f -size 44373 +oid sha256:514cc5ef29ef732a2b03010cf05a79607498919fc0c9d8afedc8713b77b24ae0 +size 44877 diff --git a/tools/localazy/config.json b/tools/localazy/config.json index a085b36619..20a53fdcf0 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -64,7 +64,8 @@ "includeRegex" : [ "screen_create_room_.*", "screen\\.create_room\\..*", - "screen_start_chat_.*" + "screen_start_chat_.*", + "screen\\.start_chat\\..*" ] }, {