Provide calculated server names when opening a room from another (#5155)
* Provide calculated server names when opening a room from another, based on the most frequently used domain names in the user ids for the users in the room. This helps when following permalinks or navigating to the successor room of a tombstoned one. Previously, the `/summary` endpoint was failing because no server names were used in the `via` parameters.
This commit is contained in:
parent
dc33a3b2d3
commit
12ea2064c7
16 changed files with 54 additions and 27 deletions
|
|
@ -335,8 +335,8 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
is NavTarget.Room -> {
|
is NavTarget.Room -> {
|
||||||
val callback = object : JoinedRoomLoadedFlowNode.Callback {
|
val callback = object : JoinedRoomLoadedFlowNode.Callback {
|
||||||
override fun onOpenRoom(roomId: RoomId) {
|
override fun onOpenRoom(roomId: RoomId, serverNames: List<String>) {
|
||||||
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias()))
|
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias(), serverNames))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onForwardedToSingleRoom(roomId: RoomId) {
|
override fun onForwardedToSingleRoom(roomId: RoomId) {
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ class JoinedRoomLoadedFlowNode @AssistedInject constructor(
|
||||||
plugins = plugins,
|
plugins = plugins,
|
||||||
), DaggerComponentOwner {
|
), DaggerComponentOwner {
|
||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
fun onOpenRoom(roomId: RoomId)
|
fun onOpenRoom(roomId: RoomId, serverNames: List<String>)
|
||||||
fun onPermalinkClick(data: PermalinkData, pushToBackstack: Boolean)
|
fun onPermalinkClick(data: PermalinkData, pushToBackstack: Boolean)
|
||||||
fun onForwardedToSingleRoom(roomId: RoomId)
|
fun onForwardedToSingleRoom(roomId: RoomId)
|
||||||
fun onOpenGlobalNotificationSettings()
|
fun onOpenGlobalNotificationSettings()
|
||||||
|
|
@ -121,8 +121,8 @@ class JoinedRoomLoadedFlowNode @AssistedInject constructor(
|
||||||
callbacks.forEach { it.onOpenGlobalNotificationSettings() }
|
callbacks.forEach { it.onOpenGlobalNotificationSettings() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOpenRoom(roomId: RoomId) {
|
override fun onOpenRoom(roomId: RoomId, serverNames: List<String>) {
|
||||||
callbacks.forEach { it.onOpenRoom(roomId) }
|
callbacks.forEach { it.onOpenRoom(roomId, serverNames) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPermalinkClick(data: PermalinkData, pushToBackstack: Boolean) {
|
override fun onPermalinkClick(data: PermalinkData, pushToBackstack: Boolean) {
|
||||||
|
|
|
||||||
|
|
@ -20,5 +20,5 @@ interface MessagesNavigator {
|
||||||
fun onReportContentClick(eventId: EventId, senderId: UserId)
|
fun onReportContentClick(eventId: EventId, senderId: UserId)
|
||||||
fun onEditPollClick(eventId: EventId)
|
fun onEditPollClick(eventId: EventId)
|
||||||
fun onPreviewAttachment(attachments: ImmutableList<Attachment>)
|
fun onPreviewAttachment(attachments: ImmutableList<Attachment>)
|
||||||
fun onNavigateToRoom(roomId: RoomId)
|
fun onNavigateToRoom(roomId: RoomId, serverNames: List<String>)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ import io.element.android.libraries.mediaplayer.api.MediaPlayer
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
@ -213,11 +214,11 @@ class MessagesNode @AssistedInject constructor(
|
||||||
callbacks.forEach { it.onPreviewAttachments(attachments) }
|
callbacks.forEach { it.onPreviewAttachments(attachments) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNavigateToRoom(roomId: RoomId) {
|
override fun onNavigateToRoom(roomId: RoomId, serverNames: List<String>) {
|
||||||
if (roomId == room.roomId) {
|
if (roomId == room.roomId) {
|
||||||
displaySameRoomToast()
|
displaySameRoomToast()
|
||||||
} else {
|
} else {
|
||||||
val permalinkData = PermalinkData.RoomLink(roomId.toRoomIdOrAlias())
|
val permalinkData = PermalinkData.RoomLink(roomId.toRoomIdOrAlias(), viaParameters = serverNames.toImmutableList())
|
||||||
callbacks.forEach { it.onPermalinkClick(permalinkData) }
|
callbacks.forEach { it.onPermalinkClick(permalinkData) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -278,7 +278,7 @@ fun MessagesView(
|
||||||
state = state,
|
state = state,
|
||||||
onLinkClick = { url, customTab -> onLinkClick(url, customTab) },
|
onLinkClick = { url, customTab -> onLinkClick(url, customTab) },
|
||||||
onRoomSuccessorClick = { roomId ->
|
onRoomSuccessorClick = { roomId ->
|
||||||
state.timelineState.eventSink(TimelineEvents.NavigateToRoom(roomId = roomId))
|
state.timelineState.eventSink(TimelineEvents.NavigateToPredecessorOrSuccessorRoom(roomId = roomId))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,11 @@ sealed interface TimelineEvents {
|
||||||
data class ComputeVerifiedUserSendFailure(val event: TimelineItem.Event) : EventFromTimelineItem
|
data class ComputeVerifiedUserSendFailure(val event: TimelineItem.Event) : EventFromTimelineItem
|
||||||
data class ShowShieldDialog(val messageShield: MessageShield) : EventFromTimelineItem
|
data class ShowShieldDialog(val messageShield: MessageShield) : EventFromTimelineItem
|
||||||
data class LoadMore(val direction: Timeline.PaginationDirection) : 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.
|
* Events coming from a poll item.
|
||||||
|
|
|
||||||
|
|
@ -178,8 +178,10 @@ class TimelinePresenter @AssistedInject constructor(
|
||||||
is TimelineEvents.ComputeVerifiedUserSendFailure -> {
|
is TimelineEvents.ComputeVerifiedUserSendFailure -> {
|
||||||
resolveVerifiedUserSendFailureState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(event.event))
|
resolveVerifiedUserSendFailureState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(event.event))
|
||||||
}
|
}
|
||||||
is TimelineEvents.NavigateToRoom -> {
|
is TimelineEvents.NavigateToPredecessorOrSuccessorRoom -> {
|
||||||
navigator.onNavigateToRoom(event.roomId)
|
// 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
|
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<String> {
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ fun TimelineItemVirtualRow(
|
||||||
roomName = timelineRoomInfo.name,
|
roomName = timelineRoomInfo.name,
|
||||||
isDm = timelineRoomInfo.isDm,
|
isDm = timelineRoomInfo.isDm,
|
||||||
onPredecessorRoomClick = { roomId ->
|
onPredecessorRoomClick = { roomId ->
|
||||||
eventSink(TimelineEvents.NavigateToRoom(roomId))
|
eventSink(TimelineEvents.NavigateToPredecessorOrSuccessorRoom(roomId))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ class FakeMessagesNavigator(
|
||||||
private val onReportContentClickLambda: (eventId: EventId, senderId: UserId) -> Unit = { _, _ -> lambdaError() },
|
private val onReportContentClickLambda: (eventId: EventId, senderId: UserId) -> Unit = { _, _ -> lambdaError() },
|
||||||
private val onEditPollClickLambda: (eventId: EventId) -> Unit = { _ -> lambdaError() },
|
private val onEditPollClickLambda: (eventId: EventId) -> Unit = { _ -> lambdaError() },
|
||||||
private val onPreviewAttachmentLambda: (attachments: ImmutableList<Attachment>) -> Unit = { _ -> lambdaError() },
|
private val onPreviewAttachmentLambda: (attachments: ImmutableList<Attachment>) -> Unit = { _ -> lambdaError() },
|
||||||
private val onNavigateToRoomLambda: (roomId: RoomId) -> Unit = { _ -> lambdaError() }
|
private val onNavigateToRoomLambda: (roomId: RoomId, serverNames: List<String>) -> Unit = { _, _ -> lambdaError() }
|
||||||
) : MessagesNavigator {
|
) : MessagesNavigator {
|
||||||
override fun onShowEventDebugInfoClick(eventId: EventId?, debugInfo: TimelineItemDebugInfo) {
|
override fun onShowEventDebugInfoClick(eventId: EventId?, debugInfo: TimelineItemDebugInfo) {
|
||||||
onShowEventDebugInfoClickLambda(eventId, debugInfo)
|
onShowEventDebugInfoClickLambda(eventId, debugInfo)
|
||||||
|
|
@ -43,7 +43,7 @@ class FakeMessagesNavigator(
|
||||||
onPreviewAttachmentLambda(attachments)
|
onPreviewAttachmentLambda(attachments)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNavigateToRoom(roomId: RoomId) {
|
override fun onNavigateToRoom(roomId: RoomId, serverNames: List<String>) {
|
||||||
onNavigateToRoomLambda(roomId)
|
onNavigateToRoomLambda(roomId, serverNames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -579,7 +579,7 @@ class MessagesViewTest {
|
||||||
val text = rule.activity.getString(R.string.screen_room_timeline_tombstoned_room_action)
|
val text = rule.activity.getString(R.string.screen_room_timeline_tombstoned_room_action)
|
||||||
// The bottomsheet subcompose seems to make the node to appear twice
|
// The bottomsheet subcompose seems to make the node to appear twice
|
||||||
rule.onAllNodesWithText(text).onFirst().performClick()
|
rule.onAllNodesWithText(text).onFirst().performClick()
|
||||||
eventsRecorder.assertSingle(TimelineEvents.NavigateToRoom(successorRoomId))
|
eventsRecorder.assertSingle(TimelineEvents.NavigateToPredecessorOrSuccessorRoom(successorRoomId))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
|
|
@ -753,18 +753,19 @@ class TimelinePresenterTest {
|
||||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val onNavigateToRoomLambda = lambdaRecorder<RoomId, Unit> {}
|
val onNavigateToRoomLambda = lambdaRecorder<RoomId, List<String>, Unit> { _, _ -> }
|
||||||
val navigator = FakeMessagesNavigator(
|
val navigator = FakeMessagesNavigator(
|
||||||
onNavigateToRoomLambda = onNavigateToRoomLambda
|
onNavigateToRoomLambda = onNavigateToRoomLambda
|
||||||
)
|
)
|
||||||
val presenter = createTimelinePresenter(room = room, messagesNavigator = navigator)
|
val presenter = createTimelinePresenter(room = room, messagesNavigator = navigator)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitFirstItem()
|
val initialState = awaitFirstItem()
|
||||||
initialState.eventSink(TimelineEvents.NavigateToRoom(A_ROOM_ID))
|
initialState.eventSink(TimelineEvents.NavigateToPredecessorOrSuccessorRoom(A_ROOM_ID))
|
||||||
assert(onNavigateToRoomLambda)
|
assert(onNavigateToRoomLambda)
|
||||||
.isCalledOnce()
|
.isCalledOnce()
|
||||||
.with(
|
.with(
|
||||||
value(A_ROOM_ID)
|
value(A_ROOM_ID),
|
||||||
|
value(emptyList<String>())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ interface RoomDetailsEntryPoint : FeatureEntryPoint {
|
||||||
|
|
||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
fun onOpenGlobalNotificationSettings()
|
fun onOpenGlobalNotificationSettings()
|
||||||
fun onOpenRoom(roomId: RoomId)
|
fun onOpenRoom(roomId: RoomId, serverNames: List<String>)
|
||||||
fun onPermalinkClick(data: PermalinkData, pushToBackstack: Boolean)
|
fun onPermalinkClick(data: PermalinkData, pushToBackstack: Boolean)
|
||||||
fun onForwardedToSingleRoom(roomId: RoomId)
|
fun onForwardedToSingleRoom(roomId: RoomId)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -270,7 +270,7 @@ class RoomDetailsFlowNode @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartDM(roomId: RoomId) {
|
override fun onStartDM(roomId: RoomId) {
|
||||||
plugins<RoomDetailsEntryPoint.Callback>().forEach { it.onOpenRoom(roomId) }
|
plugins<RoomDetailsEntryPoint.Callback>().forEach { it.onOpenRoom(roomId, emptyList()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartCall(dmRoomId: RoomId) {
|
override fun onStartCall(dmRoomId: RoomId) {
|
||||||
|
|
|
||||||
|
|
@ -29,4 +29,7 @@ value class UserId(val value: String) : Serializable {
|
||||||
get() = value
|
get() = value
|
||||||
.removePrefix("@")
|
.removePrefix("@")
|
||||||
.substringBefore(":")
|
.substringBefore(":")
|
||||||
|
|
||||||
|
val domainName: String?
|
||||||
|
get() = value.substringAfter(":").takeIf { it.isNotEmpty() }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -283,7 +283,7 @@ class RustMatrixClient(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getJoinedRoom(roomId: RoomId): JoinedRoom? = withContext(sessionDispatcher) {
|
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 -> {
|
is RoomIdOrAlias.Alias -> {
|
||||||
val roomId = innerClient.resolveRoomAlias(roomIdOrAlias.roomAlias.value)?.roomId?.let { RoomId(it) }
|
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) {
|
if (room == null) {
|
||||||
val preview = innerClient.getRoomPreviewFromRoomAlias(roomIdOrAlias.roomAlias.value)
|
val preview = innerClient.getRoomPreviewFromRoomAlias(roomIdOrAlias.roomAlias.value)
|
||||||
room = NotJoinedRustRoom(sessionId, null, RoomPreviewInfoMapper.map(preview.info()))
|
room = NotJoinedRustRoom(sessionId, null, RoomPreviewInfoMapper.map(preview.info()))
|
||||||
|
|
@ -489,7 +489,7 @@ class RustMatrixClient(
|
||||||
room
|
room
|
||||||
}
|
}
|
||||||
is RoomIdOrAlias.Id -> {
|
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) {
|
if (room == null) {
|
||||||
val preview = innerClient.getRoomPreviewFromRoomId(roomIdOrAlias.roomId.value, serverNames)
|
val preview = innerClient.getRoomPreviewFromRoomId(roomIdOrAlias.roomId.value, serverNames)
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ class RustRoomFactory(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getJoinedRoomOrPreview(roomId: RoomId): GetRoomResult? = withContext(dispatcher) {
|
suspend fun getJoinedRoomOrPreview(roomId: RoomId, serverNames: List<String>): GetRoomResult? = withContext(dispatcher) {
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
if (isDestroyed.get()) {
|
if (isDestroyed.get()) {
|
||||||
Timber.d("Room factory is destroyed, returning null for $roomId")
|
Timber.d("Room factory is destroyed, returning null for $roomId")
|
||||||
|
|
@ -132,7 +132,7 @@ class RustRoomFactory(
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
val preview = try {
|
val preview = try {
|
||||||
sdkRoom.previewRoom(via = emptyList())
|
sdkRoom.previewRoom(via = serverNames)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e, "Failed to get room preview for $roomId")
|
Timber.e(e, "Failed to get room preview for $roomId")
|
||||||
return@withContext null
|
return@withContext null
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue