diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt index b0c0b5298a..f3cc3dfd28 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt @@ -128,7 +128,15 @@ class RoomFlowNode @AssistedInject constructor( Timber.d("Room membership: ${roomInfo.map { it.currentUserMembership }}") val info = roomInfo.getOrNull() if (info?.currentUserMembership == CurrentUserMembership.JOINED) { - backstack.newRoot(NavTarget.JoinedRoom(roomId)) + if (info.isSpace) { + // It should not happen, but probably due to an issue in the sliding sync, + // we can have a space here in case the space has just been joined. + // So navigate to the JoinRoom target for now, which will + // handle the space not supported screen + backstack.newRoot(NavTarget.JoinRoom(roomId)) + } else { + backstack.newRoot(NavTarget.JoinedRoom(roomId)) + } } else { backstack.newRoot(NavTarget.JoinRoom(roomId)) } 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 5b6e5ad79a..6c2384e4e3 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 @@ -37,6 +37,7 @@ import io.element.android.features.roomdirectory.api.RoomDescription import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runUpdatingState +import io.element.android.libraries.core.meta.BuildMeta 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 @@ -57,6 +58,7 @@ class JoinRoomPresenter @AssistedInject constructor( private val matrixClient: MatrixClient, private val knockRoom: KnockRoom, private val acceptDeclineInvitePresenter: Presenter, + private val buildMeta: BuildMeta, ) : Presenter { interface Factory { fun create( @@ -135,6 +137,7 @@ class JoinRoomPresenter @AssistedInject constructor( contentState = contentState, acceptDeclineInviteState = acceptDeclineInviteState, knockAction = knockAction.value, + applicationName = buildMeta.applicationName, eventSink = ::handleEvents ) } 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 7c885214a1..9146b13513 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 @@ -32,6 +32,7 @@ data class JoinRoomState( val contentState: ContentState, val acceptDeclineInviteState: AcceptDeclineInviteState, val knockAction: AsyncAction, + val applicationName: String, val eventSink: (JoinRoomEvents) -> Unit ) { val joinAuthorisationStatus = when (contentState) { 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 615e264483..ee08ee954a 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 @@ -75,6 +75,15 @@ open class JoinRoomStateProvider : PreviewParameterProvider { aJoinRoomState( contentState = aFailureContentState(roomIdOrAlias = A_ROOM_ALIAS.toRoomIdOrAlias()) ), + aJoinRoomState( + contentState = aLoadedContentState( + roomId = RoomId("!aSpaceId:domain"), + name = "A space", + alias = null, + topic = "This is the topic of a space", + roomType = RoomType.Space, + ) + ), ) } @@ -122,6 +131,7 @@ fun aJoinRoomState( contentState = contentState, acceptDeclineInviteState = acceptDeclineInviteState, knockAction = knockAction, + applicationName = "AppName", eventSink = eventSink ) 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 7d579434f6..706dc3a355 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 @@ -20,8 +20,10 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable @@ -33,6 +35,7 @@ import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.atomic.atoms.PlaceholderAtom import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewDescriptionAtom import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewSubtitleAtom @@ -55,6 +58,7 @@ 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.matrix.api.room.RoomType import io.element.android.libraries.matrix.ui.components.InviteSenderView import io.element.android.libraries.ui.strings.CommonStrings @@ -76,7 +80,10 @@ fun JoinRoomView( JoinRoomTopBar(onBackClicked = onBackPressed) }, content = { - JoinRoomContent(contentState = state.contentState) + JoinRoomContent( + contentState = state.contentState, + applicationName = state.applicationName, + ) }, footer = { JoinRoomFooter( @@ -95,7 +102,8 @@ fun JoinRoomView( }, onRetry = { state.eventSink(JoinRoomEvents.RetryFetchingContent) - } + }, + onGoBack = onBackPressed, ) } ) @@ -116,6 +124,7 @@ private fun JoinRoomFooter( onJoinRoom: () -> Unit, onKnockRoom: () -> Unit, onRetry: () -> Unit, + onGoBack: () -> Unit, modifier: Modifier = Modifier, ) { if (state.contentState is ContentState.Failure) { @@ -125,6 +134,13 @@ private fun JoinRoomFooter( modifier = modifier.fillMaxWidth(), size = ButtonSize.Large, ) + } else if (state.contentState is ContentState.Loaded && state.contentState.roomType == RoomType.Space) { + Button( + text = stringResource(CommonStrings.action_go_back), + onClick = onGoBack, + modifier = modifier.fillMaxWidth(), + size = ButtonSize.Large, + ) } else { val joinAuthorisationStatus = state.joinAuthorisationStatus when (joinAuthorisationStatus) { @@ -171,6 +187,7 @@ private fun JoinRoomFooter( @Composable private fun JoinRoomContent( contentState: ContentState, + applicationName: String, modifier: Modifier = Modifier, ) { when (contentState) { @@ -211,6 +228,21 @@ private fun JoinRoomContent( InviteSenderView(inviteSender = inviteSender) } RoomPreviewDescriptionAtom(contentState.topic ?: "") + if (contentState.roomType == RoomType.Space) { + Spacer(modifier = Modifier.height(24.dp)) + Text( + text = stringResource(R.string.screen_join_room_space_not_supported_title), + textAlign = TextAlign.Center, + style = ElementTheme.typography.fontBodyLgMedium, + color = MaterialTheme.colorScheme.primary, + ) + Text( + text = stringResource(R.string.screen_join_room_space_not_supported_description, applicationName), + textAlign = TextAlign.Center, + style = ElementTheme.typography.fontBodyMdRegular, + color = MaterialTheme.colorScheme.secondary, + ) + } } }, memberCount = { diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt index b2f9790088..c288021cdf 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt @@ -23,6 +23,7 @@ import io.element.android.features.invite.api.response.AcceptDeclineInviteState import io.element.android.features.joinroom.impl.JoinRoomPresenter import io.element.android.features.roomdirectory.api.RoomDescription import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId @@ -37,6 +38,7 @@ object JoinRoomModule { client: MatrixClient, knockRoom: KnockRoom, acceptDeclineInvitePresenter: Presenter, + buildMeta: BuildMeta, ): JoinRoomPresenter.Factory { return object : JoinRoomPresenter.Factory { override fun create( @@ -51,6 +53,7 @@ object JoinRoomModule { matrixClient = client, knockRoom = knockRoom, acceptDeclineInvitePresenter = acceptDeclineInvitePresenter, + buildMeta = buildMeta, ) } } diff --git a/features/joinroom/impl/src/main/res/values/localazy.xml b/features/joinroom/impl/src/main/res/values/localazy.xml index 1c187d403d..103d512970 100644 --- a/features/joinroom/impl/src/main/res/values/localazy.xml +++ b/features/joinroom/impl/src/main/res/values/localazy.xml @@ -2,6 +2,8 @@ "Join room" "Knock to join" + "%1$s does not support spaces yet. You can access spaces on web." + "Spaces are not supported yet" "Click the button below and a room administrator will be notified. You’ll be able to join the conversation once approved." "You must be a member of this room to view the message history." "Want to join this room?" diff --git a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt index 165a63be77..3928ca31e8 100644 --- a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt +++ b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt @@ -24,6 +24,7 @@ import io.element.android.features.joinroom.impl.di.KnockRoom import io.element.android.features.roomdirectory.api.RoomDescription import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.core.meta.BuildMeta 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.RoomId @@ -36,6 +37,7 @@ import io.element.android.libraries.matrix.test.AN_EXCEPTION 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.FakeMatrixClient +import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.aRoomMember import io.element.android.libraries.matrix.ui.model.toInviteSender @@ -62,6 +64,7 @@ class JoinRoomPresenterTest { assertThat(state.contentState).isEqualTo(ContentState.Loading(A_ROOM_ID.toRoomIdOrAlias())) assertThat(state.joinAuthorisationStatus).isEqualTo(JoinAuthorisationStatus.Unknown) assertThat(state.acceptDeclineInviteState).isEqualTo(anAcceptDeclineInviteState()) + assertThat(state.applicationName).isEqualTo("AppName") cancelAndIgnoreRemainingEvents() } } @@ -414,6 +417,7 @@ class JoinRoomPresenterTest { roomDescription: Optional = Optional.empty(), matrixClient: MatrixClient = FakeMatrixClient(), knockRoom: KnockRoom = FakeKnockRoom(), + buildMeta: BuildMeta = aBuildMeta(applicationName = "AppName"), acceptDeclineInvitePresenter: Presenter = Presenter { anAcceptDeclineInviteState() } ): JoinRoomPresenter { return JoinRoomPresenter( @@ -422,6 +426,7 @@ class JoinRoomPresenterTest { roomDescription = roomDescription, matrixClient = matrixClient, knockRoom = knockRoom, + buildMeta = buildMeta, acceptDeclineInvitePresenter = acceptDeclineInvitePresenter ) } diff --git a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt index bd8f46295d..b4bd788286 100644 --- a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt +++ b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt @@ -21,6 +21,7 @@ import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.api.room.RoomType import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.EventsRecorder @@ -128,6 +129,21 @@ class JoinRoomViewTest { rule.clickOn(CommonStrings.action_retry) eventsRecorder.assertSingle(JoinRoomEvents.RetryFetchingContent) } + + @Test + fun `clicking on Go back when a space is displayed invokes the expected callback`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnce { + rule.setJoinRoomView( + aJoinRoomState( + contentState = aLoadedContentState(roomType = RoomType.Space), + eventSink = eventsRecorder, + ), + onBackPressed = it + ) + rule.clickOn(CommonStrings.action_go_back) + } + } } private fun AndroidComposeTestRule.setJoinRoomView( diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoomPreviewSubtitleAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoomPreviewSubtitleAtom.kt index 0548eadd17..8915b0fff4 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoomPreviewSubtitleAtom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoomPreviewSubtitleAtom.kt @@ -27,7 +27,7 @@ fun RoomPreviewSubtitleAtom(subtitle: String, modifier: Modifier = Modifier) { Text( modifier = modifier, text = subtitle, - style = ElementTheme.typography.fontBodyLgRegular, + style = ElementTheme.typography.fontBodyMdRegular, textAlign = TextAlign.Center, color = ElementTheme.colors.textSecondary, )