diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 38751c0a1e..fb9b38352c 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -335,8 +335,8 @@ class LoggedInFlowNode @AssistedInject constructor( } is NavTarget.Room -> { val callback = object : JoinedRoomLoadedFlowNode.Callback { - override fun onOpenRoom(roomId: RoomId) { - backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias())) + override fun onOpenRoom(roomId: RoomId, serverNames: List) { + backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias(), serverNames)) } override fun onForwardedToSingleRoom(roomId: RoomId) { diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt index 3e5947aada..b5d118df3c 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt @@ -69,7 +69,7 @@ class JoinedRoomLoadedFlowNode @AssistedInject constructor( plugins = plugins, ), DaggerComponentOwner { interface Callback : Plugin { - fun onOpenRoom(roomId: RoomId) + fun onOpenRoom(roomId: RoomId, serverNames: List) fun onPermalinkClick(data: PermalinkData, pushToBackstack: Boolean) fun onForwardedToSingleRoom(roomId: RoomId) fun onOpenGlobalNotificationSettings() @@ -121,8 +121,8 @@ class JoinedRoomLoadedFlowNode @AssistedInject constructor( callbacks.forEach { it.onOpenGlobalNotificationSettings() } } - override fun onOpenRoom(roomId: RoomId) { - callbacks.forEach { it.onOpenRoom(roomId) } + override fun onOpenRoom(roomId: RoomId, serverNames: List) { + callbacks.forEach { it.onOpenRoom(roomId, serverNames) } } override fun onPermalinkClick(data: PermalinkData, pushToBackstack: Boolean) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNavigator.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNavigator.kt index c66ec1ee51..d3c50bc133 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNavigator.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNavigator.kt @@ -20,5 +20,5 @@ interface MessagesNavigator { fun onReportContentClick(eventId: EventId, senderId: UserId) fun onEditPollClick(eventId: EventId) fun onPreviewAttachment(attachments: ImmutableList) - fun onNavigateToRoom(roomId: RoomId) + fun onNavigateToRoom(roomId: RoomId, serverNames: List) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt index 84d20fbf94..41b88be821 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt @@ -66,6 +66,7 @@ import io.element.android.libraries.mediaplayer.api.MediaPlayer import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.services.analytics.api.AnalyticsService import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -213,11 +214,11 @@ class MessagesNode @AssistedInject constructor( callbacks.forEach { it.onPreviewAttachments(attachments) } } - override fun onNavigateToRoom(roomId: RoomId) { + override fun onNavigateToRoom(roomId: RoomId, serverNames: List) { if (roomId == room.roomId) { displaySameRoomToast() } else { - val permalinkData = PermalinkData.RoomLink(roomId.toRoomIdOrAlias()) + val permalinkData = PermalinkData.RoomLink(roomId.toRoomIdOrAlias(), viaParameters = serverNames.toImmutableList()) callbacks.forEach { it.onPermalinkClick(permalinkData) } } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index 7c52cc9bab..fb588b5908 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -278,7 +278,7 @@ fun MessagesView( state = state, onLinkClick = { url, customTab -> onLinkClick(url, customTab) }, onRoomSuccessorClick = { roomId -> - state.timelineState.eventSink(TimelineEvents.NavigateToRoom(roomId = roomId)) + state.timelineState.eventSink(TimelineEvents.NavigateToPredecessorOrSuccessorRoom(roomId = roomId)) }, ) }, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt index da6837df21..66d8da771b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt @@ -31,7 +31,11 @@ sealed interface TimelineEvents { data class ComputeVerifiedUserSendFailure(val event: TimelineItem.Event) : EventFromTimelineItem data class ShowShieldDialog(val messageShield: MessageShield) : EventFromTimelineItem data class LoadMore(val direction: Timeline.PaginationDirection) : EventFromTimelineItem - data class NavigateToRoom(val roomId: RoomId) : EventFromTimelineItem + + /** + * Navigate to the predecessor or successor room of the current room. + */ + data class NavigateToPredecessorOrSuccessorRoom(val roomId: RoomId) : EventFromTimelineItem /** * Events coming from a poll item. diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt index d8c43e7de7..86dc1dfb1e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt @@ -178,8 +178,10 @@ class TimelinePresenter @AssistedInject constructor( is TimelineEvents.ComputeVerifiedUserSendFailure -> { resolveVerifiedUserSendFailureState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(event.event)) } - is TimelineEvents.NavigateToRoom -> { - navigator.onNavigateToRoom(event.roomId) + is TimelineEvents.NavigateToPredecessorOrSuccessorRoom -> { + // Navigate to the predecessor or successor room + val serverNames = calculateServerNamesForRoom(room) + navigator.onNavigateToRoom(event.roomId, serverNames) } } } @@ -353,3 +355,19 @@ private fun FocusRequestState.onFocusEventRender(): FocusRequestState { else -> this } } + +// Workaround for not having the server names available, get possible server names from the user ids of the room members +private fun calculateServerNamesForRoom(room: JoinedRoom): List { + // If we have no room members, return right ahead + val serverNames = room.membersStateFlow.value.roomMembers() ?: return emptyList() + + // Otherwise get the three most common server names from the user ids of the room members + return serverNames + .mapNotNull { it.userId.domainName } + .groupingBy { it } + .eachCount() + .let { map -> + map.keys.sortedByDescending { map[it] } + } + .take(3) +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemVirtualRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemVirtualRow.kt index 45b167b68f..7c0309aac4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemVirtualRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemVirtualRow.kt @@ -47,7 +47,7 @@ fun TimelineItemVirtualRow( roomName = timelineRoomInfo.name, isDm = timelineRoomInfo.isDm, onPredecessorRoomClick = { roomId -> - eventSink(TimelineEvents.NavigateToRoom(roomId)) + eventSink(TimelineEvents.NavigateToPredecessorOrSuccessorRoom(roomId)) }, ) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/FakeMessagesNavigator.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/FakeMessagesNavigator.kt index 32638d7b8d..f6e6fedbc5 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/FakeMessagesNavigator.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/FakeMessagesNavigator.kt @@ -21,7 +21,7 @@ class FakeMessagesNavigator( private val onReportContentClickLambda: (eventId: EventId, senderId: UserId) -> Unit = { _, _ -> lambdaError() }, private val onEditPollClickLambda: (eventId: EventId) -> Unit = { _ -> lambdaError() }, private val onPreviewAttachmentLambda: (attachments: ImmutableList) -> Unit = { _ -> lambdaError() }, - private val onNavigateToRoomLambda: (roomId: RoomId) -> Unit = { _ -> lambdaError() } + private val onNavigateToRoomLambda: (roomId: RoomId, serverNames: List) -> Unit = { _, _ -> lambdaError() } ) : MessagesNavigator { override fun onShowEventDebugInfoClick(eventId: EventId?, debugInfo: TimelineItemDebugInfo) { onShowEventDebugInfoClickLambda(eventId, debugInfo) @@ -43,7 +43,7 @@ class FakeMessagesNavigator( onPreviewAttachmentLambda(attachments) } - override fun onNavigateToRoom(roomId: RoomId) { - onNavigateToRoomLambda(roomId) + override fun onNavigateToRoom(roomId: RoomId, serverNames: List) { + onNavigateToRoomLambda(roomId, serverNames) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt index 51f5640ece..f8a5436ffe 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt @@ -579,7 +579,7 @@ class MessagesViewTest { val text = rule.activity.getString(R.string.screen_room_timeline_tombstoned_room_action) // The bottomsheet subcompose seems to make the node to appear twice rule.onAllNodesWithText(text).onFirst().performClick() - eventsRecorder.assertSingle(TimelineEvents.NavigateToRoom(successorRoomId)) + eventsRecorder.assertSingle(TimelineEvents.NavigateToPredecessorOrSuccessorRoom(successorRoomId)) } @Test diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt index f202fd7e6e..9ba2688d45 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt @@ -753,18 +753,19 @@ class TimelinePresenterTest { canUserSendMessageResult = { _, _ -> Result.success(true) }, ), ) - val onNavigateToRoomLambda = lambdaRecorder {} + val onNavigateToRoomLambda = lambdaRecorder, Unit> { _, _ -> } val navigator = FakeMessagesNavigator( onNavigateToRoomLambda = onNavigateToRoomLambda ) val presenter = createTimelinePresenter(room = room, messagesNavigator = navigator) presenter.test { val initialState = awaitFirstItem() - initialState.eventSink(TimelineEvents.NavigateToRoom(A_ROOM_ID)) + initialState.eventSink(TimelineEvents.NavigateToPredecessorOrSuccessorRoom(A_ROOM_ID)) assert(onNavigateToRoomLambda) .isCalledOnce() .with( - value(A_ROOM_ID) + value(A_ROOM_ID), + value(emptyList()) ) } } diff --git a/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt b/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt index 7ecde7e139..803002d642 100644 --- a/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt +++ b/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt @@ -34,7 +34,7 @@ interface RoomDetailsEntryPoint : FeatureEntryPoint { interface Callback : Plugin { fun onOpenGlobalNotificationSettings() - fun onOpenRoom(roomId: RoomId) + fun onOpenRoom(roomId: RoomId, serverNames: List) fun onPermalinkClick(data: PermalinkData, pushToBackstack: Boolean) fun onForwardedToSingleRoom(roomId: RoomId) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt index 122311040e..a802d5cd0a 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt @@ -270,7 +270,7 @@ class RoomDetailsFlowNode @AssistedInject constructor( } override fun onStartDM(roomId: RoomId) { - plugins().forEach { it.onOpenRoom(roomId) } + plugins().forEach { it.onOpenRoom(roomId, emptyList()) } } override fun onStartCall(dmRoomId: RoomId) { diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/UserId.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/UserId.kt index 61ac0afe2c..ece3a9a296 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/UserId.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/UserId.kt @@ -29,4 +29,7 @@ value class UserId(val value: String) : Serializable { get() = value .removePrefix("@") .substringBefore(":") + + val domainName: String? + get() = value.substringAfter(":").takeIf { it.isNotEmpty() } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index 50c311e56a..4b546232a3 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -283,7 +283,7 @@ class RustMatrixClient( } override suspend fun getJoinedRoom(roomId: RoomId): JoinedRoom? = withContext(sessionDispatcher) { - (roomFactory.getJoinedRoomOrPreview(roomId) as? GetRoomResult.Joined)?.joinedRoom + (roomFactory.getJoinedRoomOrPreview(roomId, emptyList()) as? GetRoomResult.Joined)?.joinedRoom } /** @@ -481,7 +481,7 @@ class RustMatrixClient( is RoomIdOrAlias.Alias -> { val roomId = innerClient.resolveRoomAlias(roomIdOrAlias.roomAlias.value)?.roomId?.let { RoomId(it) } - var room = (roomId?.let { roomFactory.getJoinedRoomOrPreview(it) } as? GetRoomResult.NotJoined)?.notJoinedRoom + var room = (roomId?.let { roomFactory.getJoinedRoomOrPreview(it, serverNames) } as? GetRoomResult.NotJoined)?.notJoinedRoom if (room == null) { val preview = innerClient.getRoomPreviewFromRoomAlias(roomIdOrAlias.roomAlias.value) room = NotJoinedRustRoom(sessionId, null, RoomPreviewInfoMapper.map(preview.info())) @@ -489,7 +489,7 @@ class RustMatrixClient( room } is RoomIdOrAlias.Id -> { - var room = (roomFactory.getJoinedRoomOrPreview(roomIdOrAlias.roomId) as? GetRoomResult.NotJoined)?.notJoinedRoom + var room = (roomFactory.getJoinedRoomOrPreview(roomIdOrAlias.roomId, serverNames) as? GetRoomResult.NotJoined)?.notJoinedRoom if (room == null) { val preview = innerClient.getRoomPreviewFromRoomId(roomIdOrAlias.roomId.value, serverNames) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt index c842c25876..dd6a6e974f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt @@ -98,7 +98,7 @@ class RustRoomFactory( ) } - suspend fun getJoinedRoomOrPreview(roomId: RoomId): GetRoomResult? = withContext(dispatcher) { + suspend fun getJoinedRoomOrPreview(roomId: RoomId, serverNames: List): GetRoomResult? = withContext(dispatcher) { mutex.withLock { if (isDestroyed.get()) { Timber.d("Room factory is destroyed, returning null for $roomId") @@ -132,7 +132,7 @@ class RustRoomFactory( ) } else { val preview = try { - sdkRoom.previewRoom(via = emptyList()) + sdkRoom.previewRoom(via = serverNames) } catch (e: Exception) { Timber.e(e, "Failed to get room preview for $roomId") return@withContext null