Skip to content

Commit 676032d

Browse files
committed
feat(join by alias) : improve state management
1 parent 41fe0f1 commit 676032d

File tree

4 files changed

+92
-34
lines changed

4 files changed

+92
-34
lines changed

features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressPresenter.kt

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package io.element.android.features.createroom.impl.joinbyaddress
99

1010
import androidx.compose.runtime.Composable
1111
import androidx.compose.runtime.LaunchedEffect
12+
import androidx.compose.runtime.derivedStateOf
1213
import androidx.compose.runtime.getValue
1314
import androidx.compose.runtime.mutableStateOf
1415
import androidx.compose.runtime.remember
@@ -22,9 +23,13 @@ import io.element.android.libraries.architecture.Presenter
2223
import io.element.android.libraries.core.data.tryOrNull
2324
import io.element.android.libraries.matrix.api.MatrixClient
2425
import io.element.android.libraries.matrix.api.core.RoomAlias
25-
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
26+
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
2627
import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper
2728
import kotlinx.coroutines.delay
29+
import kotlinx.coroutines.withTimeoutOrNull
30+
import kotlin.time.Duration.Companion.seconds
31+
32+
private const val ADDRESS_RESOLVE_TIMEOUT_IN_SECONDS = 10
2833

2934
class JoinRoomByAddressPresenter @AssistedInject constructor(
3035
@Assisted private val navigator: CreateRoomNavigator,
@@ -40,58 +45,94 @@ class JoinRoomByAddressPresenter @AssistedInject constructor(
4045
@Composable
4146
override fun present(): JoinRoomByAddressState {
4247
var address by remember { mutableStateOf("") }
43-
var addressState by remember { mutableStateOf<RoomAddressState>(RoomAddressState.Unknown) }
48+
var internalAddressState by remember { mutableStateOf<RoomAddressState>(RoomAddressState.Unknown) }
49+
var validateAddress: Boolean by remember { mutableStateOf(false) }
4450

4551
fun handleEvents(event: JoinRoomByAddressEvents) {
4652
when (event) {
4753
JoinRoomByAddressEvents.Continue -> {
48-
navigator.onDismissJoinRoomByAddress()
49-
navigator.onOpenRoom(RoomIdOrAlias.Alias(RoomAlias(address)))
54+
when (val currentState = internalAddressState) {
55+
is RoomAddressState.RoomFound -> onRoomFound(currentState)
56+
else -> validateAddress = true
57+
}
5058
}
5159
JoinRoomByAddressEvents.Dismiss -> navigator.onDismissJoinRoomByAddress()
5260
is JoinRoomByAddressEvents.UpdateAddress -> {
61+
validateAddress = false
5362
address = event.address.trim()
5463
}
5564
}
5665
}
5766

5867
RoomAddressStateEffect(
5968
fullAddress = address,
60-
onRoomAddressStateChange = { addressState = it }
69+
onRoomAddressStateChange = { addressState ->
70+
internalAddressState = addressState
71+
if (addressState is RoomAddressState.RoomFound && validateAddress) {
72+
onRoomFound(addressState)
73+
}
74+
}
6175
)
6276

77+
val addressState by remember {
78+
derivedStateOf {
79+
// We only want to show the "RoomFound" state as long as the user didn't validate the address.
80+
if (validateAddress || internalAddressState is RoomAddressState.RoomFound) {
81+
internalAddressState
82+
} else {
83+
RoomAddressState.Unknown
84+
}
85+
}
86+
}
87+
6388
return JoinRoomByAddressState(
6489
address = address,
6590
addressState = addressState,
6691
eventSink = ::handleEvents
6792
)
6893
}
6994

95+
private fun onRoomFound(state: RoomAddressState.RoomFound) {
96+
navigator.onDismissJoinRoomByAddress()
97+
navigator.onOpenRoom(state.resolved.roomId.toRoomIdOrAlias())
98+
}
99+
70100
@Composable
71101
private fun RoomAddressStateEffect(
72102
fullAddress: String,
73103
onRoomAddressStateChange: (RoomAddressState) -> Unit,
74104
) {
75105
val onChange by rememberUpdatedState(onRoomAddressStateChange)
76106
LaunchedEffect(fullAddress) {
77-
if (fullAddress.isEmpty()) {
78-
onChange(RoomAddressState.Unknown)
79-
return@LaunchedEffect
80-
}
81-
// debounce the room address validation
107+
// Whenever the address changes, reset the state to unknown
108+
onChange(RoomAddressState.Unknown)
109+
// debounce the room address resolution
82110
delay(300)
83111
val roomAlias = tryOrNull { RoomAlias(fullAddress) }
84-
if (roomAlias == null || !roomAliasHelper.isRoomAliasValid(roomAlias)) {
85-
onChange(RoomAddressState.Invalid)
112+
if (roomAlias != null && roomAliasHelper.isRoomAliasValid(roomAlias)) {
113+
onChange(RoomAddressState.Resolving)
114+
onChange(client.resolveRoomAddress(roomAlias))
86115
} else {
87-
onChange(RoomAddressState.Valid(matchingRoomFound = false))
88-
client.resolveRoomAlias(roomAlias)
89-
.onSuccess { resolved ->
90-
onChange(RoomAddressState.Valid(matchingRoomFound = resolved.isPresent))
91-
}
116+
onChange(RoomAddressState.Invalid)
92117
}
93118
}
94119
}
120+
121+
private suspend fun MatrixClient.resolveRoomAddress(roomAlias: RoomAlias): RoomAddressState {
122+
return withTimeoutOrNull(ADDRESS_RESOLVE_TIMEOUT_IN_SECONDS.seconds) {
123+
resolveRoomAlias(roomAlias)
124+
.fold(
125+
onSuccess = { resolved ->
126+
if (resolved.isPresent) {
127+
RoomAddressState.RoomFound(resolved.get())
128+
} else {
129+
RoomAddressState.RoomNotFound
130+
}
131+
},
132+
onFailure = { _ -> RoomAddressState.RoomNotFound }
133+
)
134+
} ?: RoomAddressState.RoomNotFound
135+
}
95136
}
96137

97138

features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressState.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package io.element.android.features.createroom.impl.joinbyaddress
99

1010
import androidx.compose.runtime.Immutable
11+
import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
1112

1213
data class JoinRoomByAddressState(
1314
val address: String,
@@ -19,5 +20,7 @@ data class JoinRoomByAddressState(
1920
sealed interface RoomAddressState {
2021
data object Unknown : RoomAddressState
2122
data object Invalid : RoomAddressState
22-
data class Valid(val matchingRoomFound: Boolean) : RoomAddressState
23+
data object Resolving : RoomAddressState
24+
data object RoomNotFound : RoomAddressState
25+
data class RoomFound(val resolved: ResolvedRoomAlias) : RoomAddressState
2326
}

features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressStateProvider.kt

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,21 @@
88
package io.element.android.features.createroom.impl.joinbyaddress
99

1010
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
11+
import io.element.android.libraries.matrix.api.core.RoomId
12+
import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
1113

1214
open class JoinRoomByAddressStateProvider : PreviewParameterProvider<JoinRoomByAddressState> {
1315
override val values: Sequence<JoinRoomByAddressState>
1416
get() = sequenceOf(
1517
aJoinRoomByAddressState(),
16-
aJoinRoomByAddressState("#room-"),
17-
aJoinRoomByAddressState("#room-", addressState = RoomAddressState.Invalid),
18-
aJoinRoomByAddressState("#room-name:matrix.org", addressState = RoomAddressState.Valid(true)),
19-
aJoinRoomByAddressState("#room-name-here:matrix.org", addressState = RoomAddressState.Valid(false)),
20-
// Add other states here
18+
aJoinRoomByAddressState(address = "#room-"),
19+
aJoinRoomByAddressState(address = "#room-", addressState = RoomAddressState.Invalid),
20+
aJoinRoomByAddressState(address = "#room-name:matrix.org", addressState = RoomAddressState.Resolving),
21+
aJoinRoomByAddressState(address = "#room-name-none:matrix.org", addressState = RoomAddressState.RoomNotFound),
22+
aJoinRoomByAddressState(
23+
address = "#room-name:matrix.org",
24+
addressState = RoomAddressState.RoomFound(ResolvedRoomAlias(RoomId("!aRoom:id"), emptyList())),
25+
),
2126
)
2227
}
2328

features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressView.kt

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.Spacer
1212
import androidx.compose.foundation.layout.fillMaxWidth
1313
import androidx.compose.foundation.layout.height
1414
import androidx.compose.foundation.layout.padding
15+
import androidx.compose.foundation.text.KeyboardActions
1516
import androidx.compose.foundation.text.KeyboardOptions
1617
import androidx.compose.material3.ExperimentalMaterial3Api
1718
import androidx.compose.material3.rememberModalBottomSheetState
@@ -23,7 +24,9 @@ import androidx.compose.ui.Modifier
2324
import androidx.compose.ui.focus.FocusRequester
2425
import androidx.compose.ui.focus.focusRequester
2526
import androidx.compose.ui.res.stringResource
27+
import androidx.compose.ui.text.input.ImeAction
2628
import androidx.compose.ui.text.input.KeyboardCapitalization
29+
import androidx.compose.ui.text.input.KeyboardType
2730
import androidx.compose.ui.tooling.preview.PreviewParameter
2831
import androidx.compose.ui.unit.dp
2932
import io.element.android.libraries.designsystem.preview.ElementPreview
@@ -60,13 +63,16 @@ fun JoinRoomByAddressView(
6063
requestFocus = sheetState.isVisible,
6164
onAddressChange = {
6265
state.eventSink(JoinRoomByAddressEvents.UpdateAddress(it))
63-
}
66+
},
67+
onContinue = {
68+
state.eventSink(JoinRoomByAddressEvents.Continue)
69+
},
6470
)
6571
Spacer(modifier = Modifier.height(24.dp))
6672
Button(
6773
text = stringResource(CommonStrings.action_continue),
6874
modifier = Modifier.fillMaxWidth(),
69-
enabled = state.addressState is RoomAddressState.Valid,
75+
showProgress = state.addressState is RoomAddressState.Resolving,
7076
onClick = {
7177
state.eventSink(JoinRoomByAddressEvents.Continue)
7278
}
@@ -81,6 +87,7 @@ private fun RoomAddressField(
8187
addressState: RoomAddressState,
8288
requestFocus: Boolean,
8389
onAddressChange: (String) -> Unit,
90+
onContinue: () -> Unit,
8491
modifier: Modifier = Modifier,
8592
) {
8693
val focusRequester = remember { FocusRequester() }
@@ -94,24 +101,26 @@ private fun RoomAddressField(
94101
placeholder = "Enter...",
95102
supportingText = when (addressState) {
96103
RoomAddressState.Invalid -> "Not a valid address"
97-
RoomAddressState.Unknown -> "e.g. #room-name:matrix.org"
98-
is RoomAddressState.Valid -> if (addressState.matchingRoomFound) {
99-
"Matching room found"
100-
} else {
101-
"e.g. #room-name:matrix.org"
102-
}
104+
is RoomAddressState.RoomFound -> "Matching room found"
105+
RoomAddressState.RoomNotFound -> "Room not found"
106+
RoomAddressState.Unknown, RoomAddressState.Resolving -> "e.g. #room-name:matrix.org"
103107
},
104108
validity = when (addressState) {
105-
RoomAddressState.Unknown -> null
106-
RoomAddressState.Invalid -> TextFieldValidity.Invalid
107-
is RoomAddressState.Valid -> if (addressState.matchingRoomFound) TextFieldValidity.Valid else null
109+
RoomAddressState.Unknown, RoomAddressState.Resolving -> null
110+
RoomAddressState.Invalid, RoomAddressState.RoomNotFound -> TextFieldValidity.Invalid
111+
is RoomAddressState.RoomFound -> TextFieldValidity.Valid
108112
},
109113
onValueChange = onAddressChange,
110114
singleLine = true,
111115
keyboardOptions = KeyboardOptions(
112116
capitalization = KeyboardCapitalization.None,
113117
autoCorrectEnabled = false,
118+
keyboardType = KeyboardType.Uri,
119+
imeAction = ImeAction.Go
114120
),
121+
keyboardActions = KeyboardActions(
122+
onGo = { onContinue() }
123+
)
115124
)
116125
}
117126

0 commit comments

Comments
 (0)