From 473f0e839525149a68f26daab032ac3f9651b9c8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 18 Apr 2024 15:19:03 +0200 Subject: [PATCH] Let JoinRoomView render the InviteSender --- .../joinroom/impl/JoinRoomPresenter.kt | 32 +++++++-- .../features/joinroom/impl/JoinRoomState.kt | 11 +-- .../joinroom/impl/JoinRoomStateProvider.kt | 19 +++++- .../features/joinroom/impl/JoinRoomView.kt | 16 ++++- .../joinroom/impl/JoinRoomPresenterTest.kt | 2 +- .../joinroom/impl/JoinRoomViewTest.kt | 4 +- .../impl/components/RoomSummaryRow.kt | 26 ++----- .../datasource/RoomListRoomSummaryFactory.kt | 15 +--- .../impl/model/RoomListRoomSummary.kt | 2 +- .../impl/model/RoomListRoomSummaryProvider.kt | 2 +- .../matrix/ui/components/InviteSenderView.kt | 68 +++++++++++++++++++ .../ui/{components => model}/InviteSender.kt | 15 +++- 12 files changed, 159 insertions(+), 53 deletions(-) create mode 100644 libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/InviteSenderView.kt rename libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/{components => model}/InviteSender.kt (81%) 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 93399b0446..8e85b846cc 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 @@ -38,7 +38,11 @@ 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 +import io.element.android.libraries.matrix.ui.model.InviteSender +import io.element.android.libraries.matrix.ui.model.toInviteSender +import kotlinx.coroutines.flow.first import java.util.Optional +import kotlin.jvm.optionals.getOrNull class JoinRoomPresenter @AssistedInject constructor( @Assisted private val roomId: RoomId, @@ -74,10 +78,22 @@ class JoinRoomPresenter @AssistedInject constructor( else -> { value = ContentState.Loading(roomIdOrAlias) val result = matrixClient.getRoomPreview(roomId.toRoomIdOrAlias()) - value = result.fold( - onSuccess = { it.toContentState() }, + result.fold( + onSuccess = { + value = it.toContentState(null) + if (it.isInvited) { + // Get the inviteSender + matrixClient.getRoomInfoFlow(roomId).first() + .getOrNull() + ?.inviter + ?.toInviteSender() + ?.let { inviteSender -> + value = it.toContentState(inviteSender) + } + } + }, onFailure = { throwable -> - if (throwable.message?.contains("403") == true) { + value = if (throwable.message?.contains("403") == true) { ContentState.UnknownRoom(roomIdOrAlias) } else { ContentState.Failure(roomIdOrAlias, throwable) @@ -118,7 +134,9 @@ class JoinRoomPresenter @AssistedInject constructor( } } -private fun RoomPreview.toContentState(): ContentState { +private fun RoomPreview.toContentState( + inviteSender: InviteSender? +): ContentState { return ContentState.Loaded( roomId = roomId, name = name, @@ -128,7 +146,7 @@ private fun RoomPreview.toContentState(): ContentState { isDirect = false, roomAvatarUrl = avatarUrl, joinAuthorisationStatus = when { - isInvited -> JoinAuthorisationStatus.IsInvited + isInvited -> JoinAuthorisationStatus.IsInvited(inviteSender) canKnock -> JoinAuthorisationStatus.CanKnock isPublic -> JoinAuthorisationStatus.CanJoin else -> JoinAuthorisationStatus.Unknown @@ -165,7 +183,9 @@ internal fun MatrixRoomInfo.toContentState(): ContentState { isDirect = isDirect, roomAvatarUrl = avatarUrl, joinAuthorisationStatus = when { - currentUserMembership == CurrentUserMembership.INVITED -> JoinAuthorisationStatus.IsInvited + currentUserMembership == CurrentUserMembership.INVITED -> JoinAuthorisationStatus.IsInvited( + inviteSender = inviter?.toInviteSender() + ) isPublic -> JoinAuthorisationStatus.CanJoin else -> JoinAuthorisationStatus.Unknown } 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 4d91135e9a..14f0cf2012 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 @@ -23,6 +23,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize 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.ui.model.InviteSender @Immutable data class JoinRoomState( @@ -71,9 +72,9 @@ sealed interface ContentState { } } -enum class JoinAuthorisationStatus { - IsInvited, - CanKnock, - CanJoin, - Unknown, +sealed interface JoinAuthorisationStatus { + data class IsInvited(val inviteSender: InviteSender?) : JoinAuthorisationStatus + data object CanKnock : JoinAuthorisationStatus + data object CanJoin : JoinAuthorisationStatus + data object Unknown : JoinAuthorisationStatus } 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 91c7ea1e37..9fec1683be 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 @@ -19,10 +19,14 @@ package io.element.android.features.joinroom.impl import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.invite.api.response.AcceptDeclineInviteState import io.element.android.features.invite.api.response.anAcceptDeclineInviteState +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.designsystem.components.avatar.AvatarSize 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.UserId import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias +import io.element.android.libraries.matrix.ui.model.InviteSender open class JoinRoomStateProvider : PreviewParameterProvider { override val values: Sequence @@ -48,7 +52,10 @@ open class JoinRoomStateProvider : PreviewParameterProvider { ) ), aJoinRoomState( - contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited) + contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited(null)) + ), + aJoinRoomState( + contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited(anInviteSender())) ), aJoinRoomState( contentState = aFailureContentState() @@ -102,5 +109,15 @@ fun aJoinRoomState( eventSink = eventSink ) +internal fun anInviteSender( + userId: UserId = UserId("@bob:domain"), + displayName: String = "Bob", + avatarData: AvatarData = AvatarData(userId.value, displayName, size = AvatarSize.InviteSender), +) = InviteSender( + userId = userId, + displayName = displayName, + avatarData = avatarData, +) + 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 d13fa3902b..f0e947014a 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 @@ -17,11 +17,13 @@ package io.element.android.features.joinroom.impl import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth 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 import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign @@ -47,6 +49,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.ui.components.InviteSenderView import io.element.android.libraries.ui.strings.CommonStrings @Composable @@ -103,7 +106,7 @@ private fun JoinRoomFooter( } else { val joinAuthorisationStatus = state.joinAuthorisationStatus when (joinAuthorisationStatus) { - JoinAuthorisationStatus.IsInvited -> { + is JoinAuthorisationStatus.IsInvited -> { ButtonRowMolecule(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(20.dp)) { OutlinedButton( text = stringResource(CommonStrings.action_decline), @@ -159,7 +162,16 @@ private fun JoinRoomContent( RoomPreviewTitleAtom(contentState.computedTitle) }, subtitle = { - RoomPreviewSubtitleAtom(contentState.computedSubtitle) + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + RoomPreviewSubtitleAtom(contentState.computedSubtitle) + val inviteSender = (contentState.joinAuthorisationStatus as? JoinAuthorisationStatus.IsInvited)?.inviteSender + if (inviteSender != null) { + InviteSenderView(inviteSender = inviteSender) + } + } }, description = { RoomPreviewDescriptionAtom(contentState.topic ?: "") 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 ff5a1b10b2..e2fa92a8a3 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 @@ -101,7 +101,7 @@ class JoinRoomPresenterTest { presenter.test { skipItems(1) awaitItem().also { state -> - assertThat(state.joinAuthorisationStatus).isEqualTo(JoinAuthorisationStatus.IsInvited) + assertThat(state.joinAuthorisationStatus).isEqualTo(JoinAuthorisationStatus.IsInvited(null)) } } } 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 a139808978..c8415e4b26 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 @@ -80,7 +80,7 @@ class JoinRoomViewTest { val eventsRecorder = EventsRecorder() rule.setJoinRoomView( aJoinRoomState( - contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited), + contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited(null)), eventSink = eventsRecorder, ), ) @@ -93,7 +93,7 @@ class JoinRoomViewTest { val eventsRecorder = EventsRecorder() rule.setJoinRoomView( aJoinRoomState( - contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited), + contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited(null)), eventSink = eventsRecorder, ), ) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt index 5ff4c5b477..77a93c46c1 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt @@ -66,7 +66,8 @@ import io.element.android.libraries.designsystem.theme.roomListRoomName import io.element.android.libraries.designsystem.theme.unreadIndicator import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.room.RoomNotificationMode -import io.element.android.libraries.matrix.ui.components.InviteSender +import io.element.android.libraries.matrix.ui.components.InviteSenderView +import io.element.android.libraries.matrix.ui.model.InviteSender import io.element.android.libraries.ui.strings.CommonStrings import timber.log.Timber @@ -96,7 +97,10 @@ internal fun RoomSummaryRow( InviteSubtitle(isDirect = room.isDirect, inviteSender = room.inviteSender, canonicalAlias = room.canonicalAlias) if (!room.isDirect && room.inviteSender != null) { Spacer(modifier = Modifier.height(4.dp)) - InviteSenderRow(sender = room.inviteSender) + InviteSenderView( + modifier = Modifier.fillMaxWidth(), + inviteSender = room.inviteSender, + ) } Spacer(modifier = Modifier.height(12.dp)) InviteButtonsRow( @@ -290,24 +294,6 @@ private fun InviteNameAndIndicatorRow( } } -@Composable -private fun InviteSenderRow( - sender: InviteSender, - modifier: Modifier = Modifier -) { - Row( - horizontalArrangement = Arrangement.spacedBy(4.dp), - modifier = modifier.fillMaxWidth(), - ) { - Avatar(avatarData = sender.avatarData) - Text( - text = sender.annotatedString(), - style = ElementTheme.typography.fontBodyMdRegular, - color = MaterialTheme.colorScheme.secondary, - ) - } -} - @Composable private fun InviteButtonsRow( onAcceptClicked: () -> Unit, diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListRoomSummaryFactory.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListRoomSummaryFactory.kt index 74af42aaa0..80232563ed 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListRoomSummaryFactory.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListRoomSummaryFactory.kt @@ -26,7 +26,7 @@ import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.roomlist.RoomSummary -import io.element.android.libraries.matrix.ui.components.InviteSender +import io.element.android.libraries.matrix.ui.model.toInviteSender import javax.inject.Inject class RoomListRoomSummaryFactory @Inject constructor( @@ -83,18 +83,7 @@ class RoomListRoomSummaryFactory @Inject constructor( hasRoomCall = roomSummary.details.hasRoomCall, isDirect = roomSummary.details.isDirect, isFavorite = roomSummary.details.isFavorite, - inviteSender = roomSummary.details.inviter?.run { - InviteSender( - userId = userId, - displayName = displayName ?: "", - avatarData = AvatarData( - id = userId.value, - name = displayName, - url = avatarUrl, - size = AvatarSize.InviteSender, - ), - ) - }, + inviteSender = roomSummary.details.inviter?.toInviteSender(), isDm = roomSummary.details.isDm, canonicalAlias = roomSummary.details.canonicalAlias, displayType = if (roomSummary.details.currentUserMembership == CurrentUserMembership.INVITED) { diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt index ab24071ecc..84c4cd45c3 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt @@ -21,7 +21,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData 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.room.RoomNotificationMode -import io.element.android.libraries.matrix.ui.components.InviteSender +import io.element.android.libraries.matrix.ui.model.InviteSender @Immutable data class RoomListRoomSummary( diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryProvider.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryProvider.kt index 7031a40c33..005044e60d 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryProvider.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryProvider.kt @@ -23,7 +23,7 @@ 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.UserId import io.element.android.libraries.matrix.api.room.RoomNotificationMode -import io.element.android.libraries.matrix.ui.components.InviteSender +import io.element.android.libraries.matrix.ui.model.InviteSender open class RoomListRoomSummaryProvider : PreviewParameterProvider { override val values: Sequence diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/InviteSenderView.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/InviteSenderView.kt new file mode 100644 index 0000000000..6e5590e1a2 --- /dev/null +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/InviteSenderView.kt @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.ui.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.designsystem.components.avatar.Avatar +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.designsystem.components.avatar.AvatarSize +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.matrix.api.core.UserId +import io.element.android.libraries.matrix.ui.model.InviteSender + +@Composable +fun InviteSenderView( + inviteSender: InviteSender, + modifier: Modifier = Modifier +) { + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp), + modifier = modifier, + ) { + Avatar(avatarData = inviteSender.avatarData) + Text( + text = inviteSender.annotatedString(), + style = ElementTheme.typography.fontBodyMdRegular, + color = MaterialTheme.colorScheme.secondary, + ) + } +} + +@PreviewsDayNight +@Composable +internal fun InviteSenderViewPreview() = ElementPreview { + InviteSenderView( + inviteSender = InviteSender( + userId = UserId("@bob:example.com"), + displayName = "Bob", + avatarData = AvatarData( + id = "@bob:example.com", + name = "Bob", + url = null, + size = AvatarSize.InviteSender + ) + ) + ) +} diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/InviteSender.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/InviteSender.kt similarity index 81% rename from libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/InviteSender.kt rename to libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/InviteSender.kt index 91c3e6ff40..410fb7edbd 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/InviteSender.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/InviteSender.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.matrix.ui.components +package io.element.android.libraries.matrix.ui.model import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable @@ -25,7 +25,9 @@ import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.font.FontWeight import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.ui.R @Immutable @@ -54,3 +56,14 @@ data class InviteSender( } } } + +fun RoomMember.toInviteSender() = InviteSender( + userId = userId, + displayName = displayName ?: "", + avatarData = AvatarData( + id = userId.value, + name = displayName, + url = avatarUrl, + size = AvatarSize.InviteSender, + ), +)