Merge pull request #3868 from element-hq/feature/fga/create_room_improve_address
Create room : improve handling of room address
This commit is contained in:
commit
ebf033f7c0
27 changed files with 342 additions and 68 deletions
|
|
@ -19,6 +19,4 @@ data class CreateRoomConfig(
|
||||||
val avatarUri: Uri? = null,
|
val avatarUri: Uri? = null,
|
||||||
val invites: ImmutableList<MatrixUser> = persistentListOf(),
|
val invites: ImmutableList<MatrixUser> = persistentListOf(),
|
||||||
val roomVisibility: RoomVisibilityState = RoomVisibilityState.Private,
|
val roomVisibility: RoomVisibilityState = RoomVisibilityState.Private,
|
||||||
) {
|
)
|
||||||
val isValid = roomName.isNullOrEmpty().not() && roomVisibility.isValid()
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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.RoomAccess
|
||||||
import io.element.android.features.createroom.impl.configureroom.RoomAccessItem
|
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.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.RoomVisibilityItem
|
||||||
import io.element.android.features.createroom.impl.configureroom.RoomVisibilityState
|
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.di.CreateRoomScope
|
||||||
import io.element.android.features.createroom.impl.userlist.UserListDataStore
|
import io.element.android.features.createroom.impl.userlist.UserListDataStore
|
||||||
import io.element.android.libraries.androidutils.file.safeDelete
|
import io.element.android.libraries.androidutils.file.safeDelete
|
||||||
import io.element.android.libraries.di.SingleIn
|
import io.element.android.libraries.di.SingleIn
|
||||||
|
import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
@ -29,6 +29,7 @@ import javax.inject.Inject
|
||||||
@SingleIn(CreateRoomScope::class)
|
@SingleIn(CreateRoomScope::class)
|
||||||
class CreateRoomDataStore @Inject constructor(
|
class CreateRoomDataStore @Inject constructor(
|
||||||
val selectedUserListDataStore: UserListDataStore,
|
val selectedUserListDataStore: UserListDataStore,
|
||||||
|
private val roomAliasHelper: RoomAliasHelper,
|
||||||
) {
|
) {
|
||||||
private val createRoomConfigFlow: MutableStateFlow<CreateRoomConfig> = MutableStateFlow(CreateRoomConfig())
|
private val createRoomConfigFlow: MutableStateFlow<CreateRoomConfig> = MutableStateFlow(CreateRoomConfig())
|
||||||
private var cachedAvatarUri: Uri? = null
|
private var cachedAvatarUri: Uri? = null
|
||||||
|
|
@ -46,13 +47,13 @@ class CreateRoomDataStore @Inject constructor(
|
||||||
|
|
||||||
fun setRoomName(roomName: String) {
|
fun setRoomName(roomName: String) {
|
||||||
createRoomConfigFlow.getAndUpdate { config ->
|
createRoomConfigFlow.getAndUpdate { config ->
|
||||||
/*
|
|
||||||
val newVisibility = when (config.roomVisibility) {
|
val newVisibility = when (config.roomVisibility) {
|
||||||
is RoomVisibilityState.Public -> {
|
is RoomVisibilityState.Public -> {
|
||||||
val roomAddress = config.roomVisibility.roomAddress
|
val roomAddress = config.roomVisibility.roomAddress
|
||||||
if (roomAddress is RoomAddress.AutoFilled || roomName.isEmpty()) {
|
if (roomAddress is RoomAddress.AutoFilled || roomName.isEmpty()) {
|
||||||
|
val roomAliasName = roomAliasHelper.roomAliasNameFromRoomDisplayName(roomName)
|
||||||
config.roomVisibility.copy(
|
config.roomVisibility.copy(
|
||||||
roomAddress = RoomAddress.AutoFilled(roomName),
|
roomAddress = RoomAddress.AutoFilled(roomAliasName),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
config.roomVisibility
|
config.roomVisibility
|
||||||
|
|
@ -60,9 +61,9 @@ class CreateRoomDataStore @Inject constructor(
|
||||||
}
|
}
|
||||||
else -> config.roomVisibility
|
else -> config.roomVisibility
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
config.copy(
|
config.copy(
|
||||||
roomName = roomName.takeIf { it.isNotEmpty() },
|
roomName = roomName.takeIf { it.isNotEmpty() },
|
||||||
|
roomVisibility = newVisibility,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -85,11 +86,13 @@ class CreateRoomDataStore @Inject constructor(
|
||||||
config.copy(
|
config.copy(
|
||||||
roomVisibility = when (visibility) {
|
roomVisibility = when (visibility) {
|
||||||
RoomVisibilityItem.Private -> RoomVisibilityState.Private
|
RoomVisibilityItem.Private -> RoomVisibilityState.Private
|
||||||
RoomVisibilityItem.Public -> RoomVisibilityState.Public(
|
RoomVisibilityItem.Public -> {
|
||||||
roomAddress = RoomAddress.AutoFilled(config.roomName.orEmpty()),
|
val roomAliasName = roomAliasHelper.roomAliasNameFromRoomDisplayName(config.roomName.orEmpty())
|
||||||
roomAddressErrorState = RoomAddressErrorState.None,
|
RoomVisibilityState.Public(
|
||||||
roomAccess = RoomAccess.Anyone,
|
roomAddress = RoomAddress.AutoFilled(roomAliasName),
|
||||||
)
|
roomAccess = RoomAccess.Anyone,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import im.vector.app.features.analytics.plan.CreatedRoom
|
import im.vector.app.features.analytics.plan.CreatedRoom
|
||||||
import io.element.android.features.createroom.impl.CreateRoomConfig
|
import io.element.android.features.createroom.impl.CreateRoomConfig
|
||||||
import io.element.android.features.createroom.impl.CreateRoomDataStore
|
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.CreateRoomParameters
|
||||||
import io.element.android.libraries.matrix.api.createroom.RoomPreset
|
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.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.matrix.ui.media.AvatarAction
|
||||||
import io.element.android.libraries.mediapickers.api.PickerProvider
|
import io.element.android.libraries.mediapickers.api.PickerProvider
|
||||||
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
|
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 io.element.android.services.analytics.api.AnalyticsService
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.util.Optional
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlin.jvm.optionals.getOrNull
|
||||||
|
|
||||||
class ConfigureRoomPresenter @Inject constructor(
|
class ConfigureRoomPresenter @Inject constructor(
|
||||||
private val dataStore: CreateRoomDataStore,
|
private val dataStore: CreateRoomDataStore,
|
||||||
|
|
@ -51,6 +57,7 @@ class ConfigureRoomPresenter @Inject constructor(
|
||||||
private val analyticsService: AnalyticsService,
|
private val analyticsService: AnalyticsService,
|
||||||
permissionsPresenterFactory: PermissionsPresenter.Factory,
|
permissionsPresenterFactory: PermissionsPresenter.Factory,
|
||||||
private val featureFlagService: FeatureFlagService,
|
private val featureFlagService: FeatureFlagService,
|
||||||
|
private val roomAliasHelper: RoomAliasHelper,
|
||||||
) : Presenter<ConfigureRoomState> {
|
) : Presenter<ConfigureRoomState> {
|
||||||
private val cameraPermissionPresenter: PermissionsPresenter = permissionsPresenterFactory.create(android.Manifest.permission.CAMERA)
|
private val cameraPermissionPresenter: PermissionsPresenter = permissionsPresenterFactory.create(android.Manifest.permission.CAMERA)
|
||||||
private var pendingPermissionRequest = false
|
private var pendingPermissionRequest = false
|
||||||
|
|
@ -58,9 +65,12 @@ class ConfigureRoomPresenter @Inject constructor(
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): ConfigureRoomState {
|
override fun present(): ConfigureRoomState {
|
||||||
val cameraPermissionState = cameraPermissionPresenter.present()
|
val cameraPermissionState = cameraPermissionPresenter.present()
|
||||||
val createRoomConfig = dataStore.createRoomConfigWithInvites.collectAsState(CreateRoomConfig())
|
val createRoomConfig by dataStore.createRoomConfigWithInvites.collectAsState(CreateRoomConfig())
|
||||||
val homeserverName = remember { matrixClient.userIdServerName() }
|
val homeserverName = remember { matrixClient.userIdServerName() }
|
||||||
val isKnockFeatureEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock).collectAsState(initial = false)
|
val isKnockFeatureEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock).collectAsState(initial = false)
|
||||||
|
val roomAddressValidity = remember {
|
||||||
|
mutableStateOf<RoomAddressValidity>(RoomAddressValidity.Unknown)
|
||||||
|
}
|
||||||
|
|
||||||
val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker(
|
val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker(
|
||||||
onResult = { uri -> if (uri != null) dataStore.setAvatarUri(uri = uri, cached = true) },
|
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) }
|
onResult = { uri -> if (uri != null) dataStore.setAvatarUri(uri = uri) }
|
||||||
)
|
)
|
||||||
|
|
||||||
val avatarActions by remember(createRoomConfig.value.avatarUri) {
|
val avatarActions by remember(createRoomConfig.avatarUri) {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
listOfNotNull(
|
listOfNotNull(
|
||||||
AvatarAction.TakePhoto,
|
AvatarAction.TakePhoto,
|
||||||
AvatarAction.ChoosePhoto,
|
AvatarAction.ChoosePhoto,
|
||||||
AvatarAction.Remove.takeIf { createRoomConfig.value.avatarUri != null },
|
AvatarAction.Remove.takeIf { createRoomConfig.avatarUri != null },
|
||||||
).toImmutableList()
|
).toImmutableList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -86,6 +96,10 @@ class ConfigureRoomPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RoomAddressValidityEffect(createRoomConfig.roomVisibility.roomAddress()) { newRoomAddressValidity ->
|
||||||
|
roomAddressValidity.value = newRoomAddressValidity
|
||||||
|
}
|
||||||
|
|
||||||
val localCoroutineScope = rememberCoroutineScope()
|
val localCoroutineScope = rememberCoroutineScope()
|
||||||
val createRoomAction: MutableState<AsyncAction<RoomId>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
val createRoomAction: MutableState<AsyncAction<RoomId>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||||
|
|
||||||
|
|
@ -102,7 +116,7 @@ class ConfigureRoomPresenter @Inject constructor(
|
||||||
is ConfigureRoomEvents.RemoveUserFromSelection -> dataStore.selectedUserListDataStore.removeUserFromSelection(event.matrixUser)
|
is ConfigureRoomEvents.RemoveUserFromSelection -> dataStore.selectedUserListDataStore.removeUserFromSelection(event.matrixUser)
|
||||||
is ConfigureRoomEvents.RoomAccessChanged -> dataStore.setRoomAccess(event.roomAccess)
|
is ConfigureRoomEvents.RoomAccessChanged -> dataStore.setRoomAccess(event.roomAccess)
|
||||||
is ConfigureRoomEvents.RoomAddressChanged -> dataStore.setRoomAddress(event.roomAddress)
|
is ConfigureRoomEvents.RoomAddressChanged -> dataStore.setRoomAddress(event.roomAddress)
|
||||||
is ConfigureRoomEvents.CreateRoom -> createRoom(createRoomConfig.value)
|
is ConfigureRoomEvents.CreateRoom -> createRoom(createRoomConfig)
|
||||||
is ConfigureRoomEvents.HandleAvatarAction -> {
|
is ConfigureRoomEvents.HandleAvatarAction -> {
|
||||||
when (event.action) {
|
when (event.action) {
|
||||||
AvatarAction.ChoosePhoto -> galleryImagePicker.launch()
|
AvatarAction.ChoosePhoto -> galleryImagePicker.launch()
|
||||||
|
|
@ -122,15 +136,49 @@ class ConfigureRoomPresenter @Inject constructor(
|
||||||
|
|
||||||
return ConfigureRoomState(
|
return ConfigureRoomState(
|
||||||
isKnockFeatureEnabled = isKnockFeatureEnabled,
|
isKnockFeatureEnabled = isKnockFeatureEnabled,
|
||||||
config = createRoomConfig.value,
|
config = createRoomConfig,
|
||||||
avatarActions = avatarActions,
|
avatarActions = avatarActions,
|
||||||
createRoomAction = createRoomAction.value,
|
createRoomAction = createRoomAction.value,
|
||||||
cameraPermissionState = cameraPermissionState,
|
cameraPermissionState = cameraPermissionState,
|
||||||
homeserverName = homeserverName,
|
homeserverName = homeserverName,
|
||||||
|
roomAddressValidity = roomAddressValidity.value,
|
||||||
eventSink = ::handleEvents,
|
eventSink = ::handleEvents,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RoomAddressValidityEffect(
|
||||||
|
roomAddress: Optional<String>,
|
||||||
|
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(
|
private fun CoroutineScope.createRoom(
|
||||||
config: CreateRoomConfig,
|
config: CreateRoomConfig,
|
||||||
createRoomAction: MutableState<AsyncAction<RoomId>>
|
createRoomAction: MutableState<AsyncAction<RoomId>>
|
||||||
|
|
@ -148,7 +196,7 @@ class ConfigureRoomPresenter @Inject constructor(
|
||||||
preset = RoomPreset.PUBLIC_CHAT,
|
preset = RoomPreset.PUBLIC_CHAT,
|
||||||
invite = config.invites.map { it.userId },
|
invite = config.invites.map { it.userId },
|
||||||
avatar = avatarUrl,
|
avatar = avatarUrl,
|
||||||
canonicalAlias = config.roomVisibility.roomAddress()
|
roomAliasName = config.roomVisibility.roomAddress()
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
CreateRoomParameters(
|
CreateRoomParameters(
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,10 @@ data class ConfigureRoomState(
|
||||||
val avatarActions: ImmutableList<AvatarAction>,
|
val avatarActions: ImmutableList<AvatarAction>,
|
||||||
val createRoomAction: AsyncAction<RoomId>,
|
val createRoomAction: AsyncAction<RoomId>,
|
||||||
val cameraPermissionState: PermissionsState,
|
val cameraPermissionState: PermissionsState,
|
||||||
|
val roomAddressValidity: RoomAddressValidity,
|
||||||
val homeserverName: String,
|
val homeserverName: String,
|
||||||
val eventSink: (ConfigureRoomEvents) -> Unit
|
val eventSink: (ConfigureRoomEvents) -> Unit
|
||||||
)
|
) {
|
||||||
|
val isValid: Boolean = config.roomName?.isNotEmpty() == true &&
|
||||||
|
(config.roomVisibility is RoomVisibilityState.Private || roomAddressValidity == RoomAddressValidity.Valid)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,8 @@ open class ConfigureRoomStateProvider : PreviewParameterProvider<ConfigureRoomSt
|
||||||
topic = "Room topic for this room when the text goes onto multiple lines and is really long, there shouldn’t be more than 3 lines",
|
topic = "Room topic for this room when the text goes onto multiple lines and is really long, there shouldn’t be more than 3 lines",
|
||||||
invites = aMatrixUserList().toImmutableList(),
|
invites = aMatrixUserList().toImmutableList(),
|
||||||
roomVisibility = RoomVisibilityState.Public(
|
roomVisibility = RoomVisibilityState.Public(
|
||||||
roomAddress = RoomAddress.AutoFilled("Room 101"),
|
roomAddress = RoomAddress.AutoFilled("Room-101"),
|
||||||
roomAccess = RoomAccess.Knocking,
|
roomAccess = RoomAccess.Knocking,
|
||||||
roomAddressErrorState = RoomAddressErrorState.None,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -40,12 +39,44 @@ open class ConfigureRoomStateProvider : PreviewParameterProvider<ConfigureRoomSt
|
||||||
topic = "Room topic for this room when the text goes onto multiple lines and is really long, there shouldn’t be more than 3 lines",
|
topic = "Room topic for this room when the text goes onto multiple lines and is really long, there shouldn’t be more than 3 lines",
|
||||||
invites = aMatrixUserList().toImmutableList(),
|
invites = aMatrixUserList().toImmutableList(),
|
||||||
roomVisibility = RoomVisibilityState.Public(
|
roomVisibility = RoomVisibilityState.Public(
|
||||||
roomAddress = RoomAddress.AutoFilled("Room 101"),
|
roomAddress = RoomAddress.AutoFilled("Room-101"),
|
||||||
roomAccess = RoomAccess.Knocking,
|
roomAccess = RoomAccess.Knocking,
|
||||||
roomAddressErrorState = RoomAddressErrorState.None,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
aConfigureRoomState(
|
||||||
|
config = CreateRoomConfig(
|
||||||
|
roomName = "Room 101",
|
||||||
|
topic = "Room topic for this room when the text goes onto multiple lines and is really long, there shouldn’t be more than 3 lines",
|
||||||
|
roomVisibility = RoomVisibilityState.Public(
|
||||||
|
roomAddress = RoomAddress.AutoFilled("Room-101"),
|
||||||
|
roomAccess = RoomAccess.Knocking,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
roomAddressValidity = RoomAddressValidity.NotAvailable,
|
||||||
|
),
|
||||||
|
aConfigureRoomState(
|
||||||
|
config = CreateRoomConfig(
|
||||||
|
roomName = "Room 101",
|
||||||
|
topic = "Room topic for this room when the text goes onto multiple lines and is really long, there shouldn’t be more than 3 lines",
|
||||||
|
roomVisibility = RoomVisibilityState.Public(
|
||||||
|
roomAddress = RoomAddress.AutoFilled("Room-101"),
|
||||||
|
roomAccess = RoomAccess.Knocking,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
roomAddressValidity = RoomAddressValidity.InvalidSymbols,
|
||||||
|
),
|
||||||
|
aConfigureRoomState(
|
||||||
|
config = CreateRoomConfig(
|
||||||
|
roomName = "Room 101",
|
||||||
|
topic = "Room topic for this room when the text goes onto multiple lines and is really long, there shouldn’t be more than 3 lines",
|
||||||
|
roomVisibility = RoomVisibilityState.Public(
|
||||||
|
roomAddress = RoomAddress.AutoFilled("Room-101"),
|
||||||
|
roomAccess = RoomAccess.Knocking,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
roomAddressValidity = RoomAddressValidity.Valid,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,6 +87,7 @@ fun aConfigureRoomState(
|
||||||
createRoomAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
|
createRoomAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
|
||||||
cameraPermissionState: PermissionsState = aPermissionsState(showDialog = false),
|
cameraPermissionState: PermissionsState = aPermissionsState(showDialog = false),
|
||||||
homeserverName: String = "matrix.org",
|
homeserverName: String = "matrix.org",
|
||||||
|
roomAddressValidity: RoomAddressValidity = RoomAddressValidity.Valid,
|
||||||
eventSink: (ConfigureRoomEvents) -> Unit = { },
|
eventSink: (ConfigureRoomEvents) -> Unit = { },
|
||||||
) = ConfigureRoomState(
|
) = ConfigureRoomState(
|
||||||
config = config,
|
config = config,
|
||||||
|
|
@ -64,5 +96,6 @@ fun aConfigureRoomState(
|
||||||
createRoomAction = createRoomAction,
|
createRoomAction = createRoomAction,
|
||||||
cameraPermissionState = cameraPermissionState,
|
cameraPermissionState = cameraPermissionState,
|
||||||
homeserverName = homeserverName,
|
homeserverName = homeserverName,
|
||||||
|
roomAddressValidity = roomAddressValidity,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.imePadding
|
import androidx.compose.foundation.layout.imePadding
|
||||||
|
|
@ -79,7 +80,7 @@ fun ConfigureRoomView(
|
||||||
modifier = modifier.clearFocusOnTap(focusManager),
|
modifier = modifier.clearFocusOnTap(focusManager),
|
||||||
topBar = {
|
topBar = {
|
||||||
ConfigureRoomToolbar(
|
ConfigureRoomToolbar(
|
||||||
isNextActionEnabled = state.config.isValid,
|
isNextActionEnabled = state.isValid,
|
||||||
onBackClick = onBackClick,
|
onBackClick = onBackClick,
|
||||||
onNextClick = {
|
onNextClick = {
|
||||||
focusManager.clearFocus()
|
focusManager.clearFocus()
|
||||||
|
|
@ -143,8 +144,10 @@ fun ConfigureRoomView(
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
address = state.config.roomVisibility.roomAddress,
|
address = state.config.roomVisibility.roomAddress,
|
||||||
homeserverName = state.homeserverName,
|
homeserverName = state.homeserverName,
|
||||||
|
addressValidity = state.roomAddressValidity,
|
||||||
onAddressChange = { state.eventSink(ConfigureRoomEvents.RoomAddressChanged(it)) },
|
onAddressChange = { state.eventSink(ConfigureRoomEvents.RoomAddressChanged(it)) },
|
||||||
)
|
)
|
||||||
|
Spacer(Modifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -319,6 +322,7 @@ private fun RoomAccessOptions(
|
||||||
private fun RoomAddressField(
|
private fun RoomAddressField(
|
||||||
address: RoomAddress,
|
address: RoomAddress,
|
||||||
homeserverName: String,
|
homeserverName: String,
|
||||||
|
addressValidity: RoomAddressValidity,
|
||||||
onAddressChange: (String) -> Unit,
|
onAddressChange: (String) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
|
@ -340,7 +344,16 @@ private fun RoomAddressField(
|
||||||
color = ElementTheme.colors.textSecondary,
|
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,
|
onValueChange = onAddressChange,
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,7 +14,6 @@ sealed interface RoomVisibilityState {
|
||||||
|
|
||||||
data class Public(
|
data class Public(
|
||||||
val roomAddress: RoomAddress,
|
val roomAddress: RoomAddress,
|
||||||
val roomAddressErrorState: RoomAddressErrorState,
|
|
||||||
val roomAccess: RoomAccess,
|
val roomAccess: RoomAccess,
|
||||||
) : RoomVisibilityState
|
) : RoomVisibilityState
|
||||||
|
|
||||||
|
|
@ -24,11 +23,4 @@ sealed interface RoomVisibilityState {
|
||||||
is Public -> Optional.of(roomAddress.value)
|
is Public -> Optional.of(roomAddress.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isValid(): Boolean {
|
|
||||||
return when (this) {
|
|
||||||
is Private -> true
|
|
||||||
is Public -> roomAddressErrorState is RoomAddressErrorState.None && roomAddress.value.isNotEmpty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.CreateRoomDataStore
|
||||||
import io.element.android.features.createroom.impl.userlist.FakeUserListPresenterFactory
|
import io.element.android.features.createroom.impl.userlist.FakeUserListPresenterFactory
|
||||||
import io.element.android.features.createroom.impl.userlist.UserListDataStore
|
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.libraries.usersearch.test.FakeUserRepository
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
@ -32,7 +33,7 @@ class AddPeoplePresenterTest {
|
||||||
presenter = AddPeoplePresenter(
|
presenter = AddPeoplePresenter(
|
||||||
FakeUserListPresenterFactory(),
|
FakeUserListPresenterFactory(),
|
||||||
FakeUserRepository(),
|
FakeUserRepository(),
|
||||||
CreateRoomDataStore(UserListDataStore())
|
CreateRoomDataStore(UserListDataStore(), FakeRoomAliasHelper())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.featureflag.test.FakeFeatureFlagService
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
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.AN_AVATAR_URL
|
||||||
import io.element.android.libraries.matrix.test.A_MESSAGE
|
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_ROOM_NAME
|
||||||
import io.element.android.libraries.matrix.test.A_THROWABLE
|
import io.element.android.libraries.matrix.test.A_THROWABLE
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
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.components.aMatrixUser
|
||||||
import io.element.android.libraries.matrix.ui.media.AvatarAction
|
import io.element.android.libraries.matrix.ui.media.AvatarAction
|
||||||
import io.element.android.libraries.mediapickers.api.PickerProvider
|
import io.element.android.libraries.mediapickers.api.PickerProvider
|
||||||
|
|
@ -44,6 +48,8 @@ import io.mockk.mockkStatic
|
||||||
import io.mockk.unmockkAll
|
import io.mockk.unmockkAll
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.advanceUntilIdle
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
|
|
@ -52,6 +58,7 @@ import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.robolectric.RobolectricTestRunner
|
import org.robolectric.RobolectricTestRunner
|
||||||
import java.io.File
|
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 = "content://uri_from_camera"
|
||||||
private const val AN_URI_FROM_CAMERA_2 = "content://uri_from_camera_2"
|
private const val AN_URI_FROM_CAMERA_2 = "content://uri_from_camera_2"
|
||||||
|
|
@ -95,21 +102,21 @@ class ConfigureRoomPresenterTest {
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = initialState()
|
val initialState = initialState()
|
||||||
var config = initialState.config
|
var config = initialState.config
|
||||||
assertThat(initialState.config.isValid).isFalse()
|
assertThat(initialState.isValid).isFalse()
|
||||||
|
|
||||||
// Room name not empty
|
// Room name not empty
|
||||||
initialState.eventSink(ConfigureRoomEvents.RoomNameChanged(A_ROOM_NAME))
|
initialState.eventSink(ConfigureRoomEvents.RoomNameChanged(A_ROOM_NAME))
|
||||||
var newState: ConfigureRoomState = awaitItem()
|
var newState: ConfigureRoomState = awaitItem()
|
||||||
config = config.copy(roomName = A_ROOM_NAME)
|
config = config.copy(roomName = A_ROOM_NAME)
|
||||||
assertThat(newState.config).isEqualTo(config)
|
assertThat(newState.config).isEqualTo(config)
|
||||||
assertThat(newState.config.isValid).isTrue()
|
assertThat(newState.isValid).isTrue()
|
||||||
|
|
||||||
// Clear room name
|
// Clear room name
|
||||||
newState.eventSink(ConfigureRoomEvents.RoomNameChanged(""))
|
newState.eventSink(ConfigureRoomEvents.RoomNameChanged(""))
|
||||||
newState = awaitItem()
|
newState = awaitItem()
|
||||||
config = config.copy(roomName = null)
|
config = config.copy(roomName = null)
|
||||||
assertThat(newState.config).isEqualTo(config)
|
assertThat(newState.config).isEqualTo(config)
|
||||||
assertThat(newState.config.isValid).isFalse()
|
assertThat(newState.isValid).isFalse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,8 +125,9 @@ class ConfigureRoomPresenterTest {
|
||||||
val userListDataStore = UserListDataStore()
|
val userListDataStore = UserListDataStore()
|
||||||
val pickerProvider = FakePickerProvider()
|
val pickerProvider = FakePickerProvider()
|
||||||
val permissionsPresenter = FakePermissionsPresenter()
|
val permissionsPresenter = FakePermissionsPresenter()
|
||||||
|
val roomAliasHelper = FakeRoomAliasHelper()
|
||||||
val presenter = createConfigureRoomPresenter(
|
val presenter = createConfigureRoomPresenter(
|
||||||
createRoomDataStore = CreateRoomDataStore(userListDataStore),
|
createRoomDataStore = CreateRoomDataStore(userListDataStore, roomAliasHelper),
|
||||||
pickerProvider = pickerProvider,
|
pickerProvider = pickerProvider,
|
||||||
permissionsPresenter = permissionsPresenter,
|
permissionsPresenter = permissionsPresenter,
|
||||||
)
|
)
|
||||||
|
|
@ -191,8 +199,7 @@ class ConfigureRoomPresenterTest {
|
||||||
newState = awaitItem()
|
newState = awaitItem()
|
||||||
expectedConfig = expectedConfig.copy(
|
expectedConfig = expectedConfig.copy(
|
||||||
roomVisibility = RoomVisibilityState.Public(
|
roomVisibility = RoomVisibilityState.Public(
|
||||||
roomAddress = RoomAddress.AutoFilled(expectedConfig.roomName ?: ""),
|
roomAddress = RoomAddress.AutoFilled(roomAliasHelper.roomAliasNameFromRoomDisplayName(expectedConfig.roomName ?: "")),
|
||||||
roomAddressErrorState = RoomAddressErrorState.None,
|
|
||||||
roomAccess = RoomAccess.Anyone,
|
roomAccess = RoomAccess.Anyone,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -254,7 +261,7 @@ class ConfigureRoomPresenterTest {
|
||||||
val matrixClient = createMatrixClient()
|
val matrixClient = createMatrixClient()
|
||||||
val analyticsService = FakeAnalyticsService()
|
val analyticsService = FakeAnalyticsService()
|
||||||
val mediaPreProcessor = FakeMediaPreProcessor()
|
val mediaPreProcessor = FakeMediaPreProcessor()
|
||||||
val createRoomDataStore = CreateRoomDataStore(UserListDataStore())
|
val createRoomDataStore = CreateRoomDataStore(UserListDataStore(), FakeRoomAliasHelper())
|
||||||
val presenter = createConfigureRoomPresenter(
|
val presenter = createConfigureRoomPresenter(
|
||||||
createRoomDataStore = createRoomDataStore,
|
createRoomDataStore = createRoomDataStore,
|
||||||
mediaPreProcessor = mediaPreProcessor,
|
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<ConfigureRoomState>.initialState(): ConfigureRoomState {
|
private suspend fun TurbineTestContext<ConfigureRoomState>.initialState(): ConfigureRoomState {
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
return awaitItem()
|
return awaitItem()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createMatrixClient() = FakeMatrixClient(
|
private fun createMatrixClient(isAliasAvailable: Boolean = true) = FakeMatrixClient(
|
||||||
userIdServerNameLambda = { "matrix.org" },
|
userIdServerNameLambda = { "matrix.org" },
|
||||||
|
resolveRoomAliasResult = {
|
||||||
|
val resolvedRoomAlias = if (isAliasAvailable) {
|
||||||
|
Optional.empty()
|
||||||
|
} else {
|
||||||
|
Optional.of(ResolvedRoomAlias(A_ROOM_ID, emptyList()))
|
||||||
|
}
|
||||||
|
Result.success(resolvedRoomAlias)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun createConfigureRoomPresenter(
|
private fun createConfigureRoomPresenter(
|
||||||
createRoomDataStore: CreateRoomDataStore = CreateRoomDataStore(UserListDataStore()),
|
roomAliasHelper: RoomAliasHelper = FakeRoomAliasHelper(),
|
||||||
|
createRoomDataStore: CreateRoomDataStore = CreateRoomDataStore(UserListDataStore(), roomAliasHelper),
|
||||||
matrixClient: MatrixClient = createMatrixClient(),
|
matrixClient: MatrixClient = createMatrixClient(),
|
||||||
pickerProvider: PickerProvider = FakePickerProvider(),
|
pickerProvider: PickerProvider = FakePickerProvider(),
|
||||||
mediaPreProcessor: MediaPreProcessor = FakeMediaPreProcessor(),
|
mediaPreProcessor: MediaPreProcessor = FakeMediaPreProcessor(),
|
||||||
|
|
@ -339,6 +417,7 @@ class ConfigureRoomPresenterTest {
|
||||||
mediaPreProcessor = mediaPreProcessor,
|
mediaPreProcessor = mediaPreProcessor,
|
||||||
analyticsService = analyticsService,
|
analyticsService = analyticsService,
|
||||||
permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionsPresenter),
|
permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionsPresenter),
|
||||||
|
roomAliasHelper = roomAliasHelper,
|
||||||
featureFlagService = FakeFeatureFlagService(
|
featureFlagService = FakeFeatureFlagService(
|
||||||
mapOf(FeatureFlags.Knock.key to isKnockFeatureEnabled)
|
mapOf(FeatureFlags.Knock.key to isKnockFeatureEnabled)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.util.Optional
|
import java.util.Optional
|
||||||
|
|
||||||
class RoomAliasResolverPresenterTest {
|
class RoomAliasHelperPresenterTest {
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val warmUpRule = WarmUpRule()
|
val warmUpRule = WarmUpRule()
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ import org.junit.rules.TestRule
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class RoomAliasResolverViewTest {
|
class RoomAliasHelperViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -168,3 +168,13 @@ fun MatrixClient.getRoomInfoFlow(roomIdOrAlias: RoomIdOrAlias): Flow<Optional<Ma
|
||||||
.map { roomSummary -> roomSummary.map { it.info } }
|
.map { roomSummary -> roomSummary.map { it.info } }
|
||||||
.distinctUntilChanged()
|
.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<RoomAlias> {
|
||||||
|
return runCatching {
|
||||||
|
RoomAlias("#$name:${userIdServerName()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,5 +20,5 @@ data class CreateRoomParameters(
|
||||||
val invite: List<UserId>? = null,
|
val invite: List<UserId>? = null,
|
||||||
val avatar: String? = null,
|
val avatar: String? = null,
|
||||||
val joinRuleOverride: JoinRuleOverride = JoinRuleOverride.None,
|
val joinRuleOverride: JoinRuleOverride = JoinRuleOverride.None,
|
||||||
val canonicalAlias: Optional<String> = Optional.empty(),
|
val roomAliasName: Optional<String> = Optional.empty(),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -334,7 +334,7 @@ class RustMatrixClient(
|
||||||
JoinRuleOverride.Knock -> RustJoinRule.Knock
|
JoinRuleOverride.Knock -> RustJoinRule.Knock
|
||||||
JoinRuleOverride.None -> null
|
JoinRuleOverride.None -> null
|
||||||
},
|
},
|
||||||
canonicalAlias = createRoomParams.canonicalAlias.getOrNull(),
|
canonicalAlias = createRoomParams.roomAliasName.getOrNull(),
|
||||||
)
|
)
|
||||||
val roomId = RoomId(client.createRoom(rustParams))
|
val roomId = RoomId(client.createRoom(rustParams))
|
||||||
// Wait to receive the room back from the sync but do not returns failure if it fails.
|
// Wait to receive the room back from the sync but do not returns failure if it fails.
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:e6270f3b7935c47da76ce4e81aa7e8eac13a9340e7ccb198f8cd6956d746dfa8
|
oid sha256:ffbdcd28c32808fba8ffb7071b6440578716d3ae89149eb319c22d3f3b78b1ff
|
||||||
size 59452
|
size 59499
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:ccbb514ad9bd6a95ecff2e278574ac5bff9bd8a63a5fc91fa35302dbaa9e71f2
|
||||||
|
size 55276
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:a2ea9839e3dd1288d99c2115c37bc7fe72716af9785c4de3e328a77a57d12d70
|
||||||
|
size 56633
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:5b28da0e8ff5cbedf8777b5af8ff6bab2bd5c476df80878b2c28dd96a354d978
|
||||||
|
size 54383
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:c3b02b0bc0d557bac4d5aa945daf04d5d58dab44d9e4284d030804e6b5ed6a49
|
oid sha256:9dc54d0a6c3cb3069482b1c27ed6d80fd63b0bcad1a550bf5c4edb09f69db3a7
|
||||||
size 60703
|
size 60749
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:71c42c595070733ac8ac2c50da116e73ea6eea7dc120f6f0d28022b5a5f925bd
|
||||||
|
size 57099
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:e10c1c3f2b4045d20c9513c70bc4ea041e091976df5385b93d45c2d03b8f6e5f
|
||||||
|
size 58513
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:cb54ae79de75af32e32642063d9dc9e0c24b0089f187a043f0fc330151c72c75
|
||||||
|
size 56170
|
||||||
Loading…
Add table
Add a link
Reference in a new issue