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 b8d5916d57..68c1d0e946 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,7 +17,6 @@ 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 @@ -32,9 +31,10 @@ 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.room.alias.RoomAliasHelper -import io.element.android.libraries.matrix.api.roomAliasFromName import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility import io.element.android.libraries.matrix.ui.media.AvatarAction +import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity +import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidityEffect import io.element.android.libraries.mediapickers.api.PickerProvider import io.element.android.libraries.mediaupload.api.MediaPreProcessor import io.element.android.libraries.permissions.api.PermissionsEvents @@ -42,12 +42,10 @@ 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 +import kotlin.jvm.optionals.getOrDefault class ConfigureRoomPresenter @Inject constructor( private val dataStore: CreateRoomDataStore, @@ -96,7 +94,12 @@ class ConfigureRoomPresenter @Inject constructor( } } - RoomAddressValidityEffect(createRoomConfig.roomVisibility.roomAddress()) { newRoomAddressValidity -> + RoomAddressValidityEffect( + client = matrixClient, + roomAliasHelper = roomAliasHelper, + newRoomAddress = createRoomConfig.roomVisibility.roomAddress().getOrDefault(""), + knownRoomAddress = null, + ) { newRoomAddressValidity -> roomAddressValidity.value = newRoomAddressValidity } @@ -146,39 +149,6 @@ class ConfigureRoomPresenter @Inject constructor( ) } - @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> 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 794c40cb4b..6651d16604 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 @@ -11,6 +11,7 @@ import io.element.android.features.createroom.impl.CreateRoomConfig import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.ui.media.AvatarAction +import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity import io.element.android.libraries.permissions.api.PermissionsState import kotlinx.collections.immutable.ImmutableList 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 6b9219f57d..71568dbbc7 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 @@ -13,6 +13,7 @@ import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.ui.components.aMatrixUserList import io.element.android.libraries.matrix.ui.media.AvatarAction +import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity import io.element.android.libraries.permissions.api.PermissionsState import io.element.android.libraries.permissions.api.aPermissionsState import kotlinx.collections.immutable.toImmutableList 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 d92051437a..2684c8334d 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 @@ -16,7 +16,6 @@ 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 import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState @@ -58,6 +57,7 @@ import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.ui.components.AvatarActionBottomSheet import io.element.android.libraries.matrix.ui.components.SelectedUsersRowList import io.element.android.libraries.matrix.ui.components.UnsavedAvatar +import io.element.android.libraries.matrix.ui.room.address.RoomAddressField import io.element.android.libraries.permissions.api.PermissionsView import io.element.android.libraries.ui.strings.CommonStrings @@ -142,10 +142,12 @@ fun ConfigureRoomView( ) RoomAddressField( modifier = Modifier.padding(horizontal = 16.dp), - address = state.config.roomVisibility.roomAddress, + address = state.config.roomVisibility.roomAddress.value, homeserverName = state.homeserverName, addressValidity = state.roomAddressValidity, onAddressChange = { state.eventSink(ConfigureRoomEvents.RoomAddressChanged(it)) }, + label = stringResource(R.string.screen_create_room_room_address_section_title), + supportingText = stringResource(R.string.screen_create_room_room_address_section_footer), ) Spacer(Modifier) } @@ -318,47 +320,6 @@ private fun RoomAccessOptions( } } -@Composable -private fun RoomAddressField( - address: RoomAddress, - homeserverName: String, - addressValidity: RoomAddressValidity, - onAddressChange: (String) -> Unit, - modifier: Modifier = Modifier, -) { - TextField( - modifier = modifier.fillMaxWidth(), - value = address.value, - label = stringResource(R.string.screen_create_room_room_address_section_title), - leadingIcon = { - Text( - text = "#", - style = ElementTheme.typography.fontBodyLgMedium, - color = ElementTheme.colors.textSecondary, - ) - }, - trailingIcon = { - Text( - text = homeserverName, - style = ElementTheme.typography.fontBodyLgMedium, - color = ElementTheme.colors.textSecondary, - ) - }, - supportingText = when (addressValidity) { - RoomAddressValidity.InvalidSymbols -> { - stringResource(CommonStrings.error_room_address_invalid_symbols) - } - RoomAddressValidity.NotAvailable -> { - stringResource(CommonStrings.error_room_address_already_exists) - } - else -> stringResource(R.string.screen_create_room_room_address_section_footer) - }, - isError = addressValidity.isError(), - onValueChange = onAddressChange, - singleLine = true, - ) -} - @PreviewWithLargeHeight @Composable internal fun ConfigureRoomViewLightPreview(@PreviewParameter(ConfigureRoomStateProvider::class) state: ConfigureRoomState) = 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 new file mode 100644 index 0000000000..590c170474 --- /dev/null +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressField.kt @@ -0,0 +1,74 @@ +/* + * 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.libraries.matrix.ui.room.address + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import io.element.android.compound.theme.ElementTheme +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.ui.strings.CommonStrings + +@Composable +fun RoomAddressField( + address: String, + homeserverName: String, + addressValidity: RoomAddressValidity, + onAddressChange: (String) -> Unit, + label: String, + supportingText: String, + modifier: Modifier = Modifier, +) { + TextField( + modifier = modifier, + value = address, + label = label, + leadingIcon = { + Text( + text = "#", + style = ElementTheme.typography.fontBodyLgMedium, + color = ElementTheme.colors.textSecondary, + ) + }, + trailingIcon = { + Text( + text = homeserverName, + style = ElementTheme.typography.fontBodyLgMedium, + color = ElementTheme.colors.textSecondary, + ) + }, + supportingText = when (addressValidity) { + RoomAddressValidity.InvalidSymbols -> { + stringResource(CommonStrings.error_room_address_invalid_symbols) + } + RoomAddressValidity.NotAvailable -> { + stringResource(CommonStrings.error_room_address_already_exists) + } + else -> supportingText + }, + isError = addressValidity.isError(), + onValueChange = onAddressChange, + singleLine = true, + ) +} + +@PreviewsDayNight +@Composable +fun RoomAddressFieldPreview() = ElementPreview { + RoomAddressField( + address = "room", + homeserverName = "element.io", + addressValidity = RoomAddressValidity.Valid, + onAddressChange = {}, + label = "Room address", + supportingText = "This is the address that people will use to join your room", + ) +} diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAddressValidity.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressValidity.kt similarity index 91% rename from features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAddressValidity.kt rename to libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressValidity.kt index 903173dc81..25af0ab98d 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAddressValidity.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressValidity.kt @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.createroom.impl.configureroom +package io.element.android.libraries.matrix.ui.room.address import androidx.compose.runtime.Immutable diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressValidityEffect.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressValidityEffect.kt new file mode 100644 index 0000000000..73c14c50df --- /dev/null +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressValidityEffect.kt @@ -0,0 +1,52 @@ +/* + * 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.libraries.matrix.ui.room.address + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberUpdatedState +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper +import io.element.android.libraries.matrix.api.roomAliasFromName +import kotlinx.coroutines.delay + +@Composable +fun RoomAddressValidityEffect( + client: MatrixClient, + roomAliasHelper: RoomAliasHelper, + newRoomAddress: String, + knownRoomAddress: String?, + onRoomAddressValidityChange: (RoomAddressValidity) -> Unit, +) { + val onChange by rememberUpdatedState(onRoomAddressValidityChange) + LaunchedEffect(newRoomAddress) { + if (newRoomAddress.isEmpty() || newRoomAddress == knownRoomAddress) { + onChange(RoomAddressValidity.Unknown) + return@LaunchedEffect + } + // debounce the room address validation + delay(300) + val roomAlias = client.roomAliasFromName(newRoomAddress).getOrNull() + if (roomAlias == null || !roomAliasHelper.isRoomAliasValid(roomAlias)) { + onChange(RoomAddressValidity.InvalidSymbols) + } else { + client.resolveRoomAlias(roomAlias) + .onSuccess { resolved -> + if (resolved.isPresent) { + onChange(RoomAddressValidity.NotAvailable) + } else { + onChange(RoomAddressValidity.Valid) + } + } + .onFailure { + onChange(RoomAddressValidity.Valid) + } + } + } +}