diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomEvents.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomEvents.kt index 999030cd50..2dddb22e13 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomEvents.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomEvents.kt @@ -17,6 +17,7 @@ package io.element.android.features.joinroom.impl sealed interface JoinRoomEvents { + data object Retry : JoinRoomEvents data object JoinRoom : JoinRoomEvents data object AcceptInvite : JoinRoomEvents data object DeclineInvite : JoinRoomEvents diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt index 7bc0f0facf..504294037f 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt @@ -20,8 +20,11 @@ import androidx.annotation.VisibleForTesting import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.produceState +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents @@ -32,6 +35,7 @@ import io.element.android.libraries.architecture.Presenter 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.RoomIdOrAlias +import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.MatrixRoomInfo import io.element.android.libraries.matrix.api.room.preview.RoomPreview @@ -56,8 +60,13 @@ class JoinRoomPresenter @AssistedInject constructor( @Composable override fun present(): JoinRoomState { val coroutineScope = rememberCoroutineScope() + var retryCount by remember { mutableIntStateOf(0) } val roomInfo by matrixClient.getRoomInfoFlow(roomId).collectAsState(initial = Optional.empty()) - val contentState by produceState(initialValue = ContentState.Loading(roomIdOrAlias), key1 = roomInfo) { + val contentState by produceState( + initialValue = ContentState.Loading(roomIdOrAlias), + key1 = roomInfo, + key2 = retryCount, + ) { value = when { roomInfo.isPresent -> { roomInfo.get().toContentState() @@ -67,10 +76,17 @@ class JoinRoomPresenter @AssistedInject constructor( } else -> { coroutineScope.launch { - val result = matrixClient.getRoomPreview(roomIdOrAlias) - value = result.getOrNull() - ?.toContentState() - ?: ContentState.UnknownRoom(roomIdOrAlias) + val result = matrixClient.getRoomPreview(roomId.toRoomIdOrAlias()) + value = result.fold( + onSuccess = { it.toContentState() }, + onFailure = { throwable -> + if (throwable.message?.contains("403") == true) { + ContentState.UnknownRoom(roomIdOrAlias) + } else { + ContentState.Failure(roomIdOrAlias, throwable) + } + } + ) } ContentState.Loading(roomIdOrAlias) } @@ -93,6 +109,9 @@ class JoinRoomPresenter @AssistedInject constructor( AcceptDeclineInviteEvents.DeclineInvite(inviteData) ) } + JoinRoomEvents.Retry -> { + retryCount++ + } } } diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt index bf57a0fdcf..4d91135e9a 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt @@ -38,6 +38,7 @@ data class JoinRoomState( sealed interface ContentState { data class Loading(val roomIdOrAlias: RoomIdOrAlias) : ContentState + data class Failure(val roomIdOrAlias: RoomIdOrAlias, val error: Throwable) : ContentState data class UnknownRoom(val roomIdOrAlias: RoomIdOrAlias) : ContentState data class Loaded( val roomId: RoomId, diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt index 3eea5e723b..7c34a05c6b 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt @@ -21,6 +21,7 @@ import io.element.android.features.invite.api.response.AcceptDeclineInviteState import io.element.android.features.invite.api.response.anAcceptDeclineInviteState import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.RoomIdOrAlias import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias open class JoinRoomStateProvider : PreviewParameterProvider { @@ -41,9 +42,24 @@ open class JoinRoomStateProvider : PreviewParameterProvider { aJoinRoomState( contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited) ), + aJoinRoomState( + contentState = aFailureContentState() + ), + aJoinRoomState( + contentState = aFailureContentState(roomIdOrAlias = A_ROOM_ALIAS.toRoomIdOrAlias()) + ), ) } +fun aFailureContentState( + roomIdOrAlias: RoomIdOrAlias = A_ROOM_ID.toRoomIdOrAlias() +): ContentState { + return ContentState.Failure( + roomIdOrAlias = roomIdOrAlias, + error = Exception("Error"), + ) +} + fun anUnknownContentState(roomId: RoomId = A_ROOM_ID) = ContentState.UnknownRoom(roomId.toRoomIdOrAlias()) fun aLoadingContentState(roomId: RoomId = A_ROOM_ID) = ContentState.Loading(roomId.toRoomIdOrAlias()) @@ -79,3 +95,4 @@ fun aJoinRoomState( ) private val A_ROOM_ID = RoomId("!exa:matrix.org") +private val A_ROOM_ALIAS = RoomAlias("#exa:matrix.org") diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt index 31f065c0e5..74c78d09a5 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt @@ -28,6 +28,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -52,6 +53,7 @@ import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.OutlinedButton import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TopAppBar +import io.element.android.libraries.matrix.api.core.RoomIdOrAlias import io.element.android.libraries.ui.strings.CommonStrings @Composable @@ -71,7 +73,7 @@ fun JoinRoomView( }, footer = { JoinRoomFooter( - joinAuthorisationStatus = state.joinAuthorisationStatus, + state = state, onAcceptInvite = { state.eventSink(JoinRoomEvents.AcceptInvite) }, @@ -81,6 +83,9 @@ fun JoinRoomView( onJoinRoom = { state.eventSink(JoinRoomEvents.JoinRoom) }, + onRetry = { + state.eventSink(JoinRoomEvents.Retry) + } ) } ) @@ -88,46 +93,57 @@ fun JoinRoomView( @Composable private fun JoinRoomFooter( - joinAuthorisationStatus: JoinAuthorisationStatus, + state: JoinRoomState, onAcceptInvite: () -> Unit, onDeclineInvite: () -> Unit, onJoinRoom: () -> Unit, + onRetry: () -> Unit, modifier: Modifier = Modifier, ) { - when (joinAuthorisationStatus) { - JoinAuthorisationStatus.IsInvited -> { - ButtonRowMolecule(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(20.dp)) { - OutlinedButton( - text = stringResource(CommonStrings.action_decline), - onClick = onDeclineInvite, - modifier = Modifier.weight(1f), + if (state.contentState is ContentState.Failure) { + Button( + text = stringResource(CommonStrings.action_retry), + onClick = onRetry, + modifier = modifier.fillMaxWidth(), + size = ButtonSize.Medium, + ) + } else { + val joinAuthorisationStatus = state.joinAuthorisationStatus + when (joinAuthorisationStatus) { + JoinAuthorisationStatus.IsInvited -> { + ButtonRowMolecule(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(20.dp)) { + OutlinedButton( + text = stringResource(CommonStrings.action_decline), + onClick = onDeclineInvite, + modifier = Modifier.weight(1f), + size = ButtonSize.Medium, + ) + Button( + text = stringResource(CommonStrings.action_accept), + onClick = onAcceptInvite, + modifier = Modifier.weight(1f), + size = ButtonSize.Medium, + ) + } + } + JoinAuthorisationStatus.CanJoin -> { + Button( + text = stringResource(R.string.screen_join_room_join_action), + onClick = onJoinRoom, + modifier = modifier.fillMaxWidth(), size = ButtonSize.Medium, ) + } + JoinAuthorisationStatus.CanKnock -> { Button( - text = stringResource(CommonStrings.action_accept), - onClick = onAcceptInvite, - modifier = Modifier.weight(1f), + text = stringResource(R.string.screen_join_room_knock_action), + onClick = onJoinRoom, + modifier = modifier.fillMaxWidth(), size = ButtonSize.Medium, ) } + JoinAuthorisationStatus.Unknown -> Unit } - JoinAuthorisationStatus.CanJoin -> { - Button( - text = stringResource(R.string.screen_join_room_join_action), - onClick = onJoinRoom, - modifier = modifier.fillMaxWidth(), - size = ButtonSize.Medium, - ) - } - JoinAuthorisationStatus.CanKnock -> { - Button( - text = stringResource(R.string.screen_join_room_knock_action), - onClick = onJoinRoom, - modifier = modifier.fillMaxWidth(), - size = ButtonSize.Medium, - ) - } - JoinAuthorisationStatus.Unknown -> Unit } } @@ -187,6 +203,32 @@ private fun JoinRoomContent( }, ) } + is ContentState.Failure -> { + ContentScaffold( + modifier = modifier, + avatar = { + PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp) + }, + title = { + when (contentState.roomIdOrAlias) { + is RoomIdOrAlias.Alias -> { + Title(contentState.roomIdOrAlias.identifier) + } + is RoomIdOrAlias.Id -> { + PlaceholderAtom(width = 200.dp, height = 22.dp) + } + } + }, + subtitle = { + Text( + text = "Failed to get information about the room", + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.error, + ) + }, + ) + } + } }