feature (room upgrade) : manage navigation and clean code.

This commit is contained in:
ganfra 2025-06-11 13:32:12 +02:00
parent 94e678a905
commit e8bc54cf9e
13 changed files with 69 additions and 54 deletions

View file

@ -9,6 +9,7 @@ package io.element.android.features.messages.impl
import io.element.android.features.messages.impl.attachments.Attachment
import io.element.android.libraries.matrix.api.core.EventId
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.timeline.item.TimelineItemDebugInfo
import kotlinx.collections.immutable.ImmutableList
@ -19,4 +20,5 @@ interface MessagesNavigator {
fun onReportContentClick(eventId: EventId, senderId: UserId)
fun onEditPollClick(eventId: EventId)
fun onPreviewAttachment(attachments: ImmutableList<Attachment>)
fun onNavigateToRoom(roomId: RoomId)
}

View file

@ -49,6 +49,7 @@ import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.di.annotations.SessionCoroutineScope
import io.element.android.libraries.matrix.api.analytics.toAnalyticsViewRoom
@ -71,6 +72,7 @@ import kotlinx.coroutines.launch
class MessagesNode @AssistedInject constructor(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
@ApplicationContext private val context: Context,
@SessionCoroutineScope
private val sessionCoroutineScope: CoroutineScope,
private val room: BaseRoom,
@ -144,15 +146,6 @@ class MessagesNode @AssistedInject constructor(
callbacks.forEach { it.onUserDataClick(userId) }
}
private fun onRoomDataClick(
activity: Activity,
eventSink: (TimelineEvents) -> Unit,
roomId: RoomId
) {
val roomLink = PermalinkData.RoomLink(roomId.toRoomIdOrAlias())
handleRoomLinkClick(activity, roomLink, eventSink)
}
private fun onLinkClick(
activity: Activity,
darkTheme: Boolean,
@ -167,7 +160,7 @@ class MessagesNode @AssistedInject constructor(
callbacks.forEach { it.onUserDataClick(permalink.userId) }
}
is PermalinkData.RoomLink -> {
handleRoomLinkClick(activity, permalink, eventSink)
handleRoomLinkClick(permalink, eventSink)
}
is PermalinkData.FallbackLink -> {
if (customTab) {
@ -183,7 +176,6 @@ class MessagesNode @AssistedInject constructor(
}
private fun handleRoomLinkClick(
context: Context,
roomLink: PermalinkData.RoomLink,
eventSink: (TimelineEvents) -> Unit,
) {
@ -193,7 +185,7 @@ class MessagesNode @AssistedInject constructor(
eventSink(TimelineEvents.FocusOnEvent(eventId))
} else {
// Click on the same room, ignore
context.toast("Already viewing this room!")
context.sameRoomToast()
}
} else {
callbacks.forEach { it.onPermalinkClick(roomLink) }
@ -220,6 +212,15 @@ class MessagesNode @AssistedInject constructor(
callbacks.forEach { it.onPreviewAttachments(attachments) }
}
override fun onNavigateToRoom(roomId: RoomId) {
if (roomId == room.roomId) {
context.sameRoomToast()
} else {
val permalinkData = PermalinkData.RoomLink(roomId.toRoomIdOrAlias())
callbacks.forEach { it.onPermalinkClick(permalinkData) }
}
}
private fun onViewAllPinnedMessagesClick() {
callbacks.forEach { it.onViewAllPinnedEvents() }
}
@ -240,6 +241,10 @@ class MessagesNode @AssistedInject constructor(
callbacks.forEach { it.onViewKnockRequests() }
}
private fun Context.sameRoomToast() {
context.toast("Already viewing this room!")
}
@Composable
override fun View(modifier: Modifier) {
val activity = requireNotNull(LocalActivity.current)
@ -260,19 +265,18 @@ class MessagesNode @AssistedInject constructor(
onRoomDetailsClick = this::onRoomDetailsClick,
onEventContentClick = this::onEventClick,
onUserDataClick = this::onUserDataClick,
onRoomDataClick = {roomId -> onRoomDataClick(activity, state.timelineState.eventSink, roomId) },
onLinkClick = { url, customTab -> onLinkClick(activity, isDark, url, state.timelineState.eventSink, customTab) },
onSendLocationClick = this::onSendLocationClick,
onCreatePollClick = this::onCreatePollClick,
onJoinCallClick = this::onJoinCallClick,
onViewAllPinnedMessagesClick = this::onViewAllPinnedMessagesClick,
modifier = modifier,
knockRequestsBannerView = {
knockRequestsBannerRenderer.View(
modifier = Modifier,
onViewRequestsClick = this::onViewKnockRequestsClick
)
},
modifier = modifier,
)
roomMemberModerationRenderer.Render(
state = state.roomMemberModerationState,

View file

@ -123,7 +123,6 @@ fun MessagesView(
onRoomDetailsClick: () -> Unit,
onEventContentClick: (isLive: Boolean, event: TimelineItem.Event) -> Boolean,
onUserDataClick: (UserId) -> Unit,
onRoomDataClick: (RoomId) -> Unit,
onLinkClick: (String, Boolean) -> Unit,
onSendLocationClick: () -> Unit,
onCreatePollClick: () -> Unit,
@ -210,11 +209,10 @@ fun MessagesView(
MessagesViewContent(
state = state,
modifier = Modifier
.padding(padding)
.consumeWindowInsets(padding),
.padding(padding)
.consumeWindowInsets(padding),
onContentClick = ::onContentClick,
onMessageLongClick = ::onMessageLongClick,
onRoomSuccessorClicked = onRoomDataClick,
onUserDataClick = {
hidingKeyboard {
state.eventSink(MessagesEvents.OnUserClicked(it))
@ -305,7 +303,6 @@ private fun MessagesViewContent(
state: MessagesState,
onContentClick: (TimelineItem.Event) -> Unit,
onUserDataClick: (MatrixUser) -> Unit,
onRoomSuccessorClicked: (RoomId) -> Unit,
onLinkClick: (Link, Boolean) -> Unit,
onReactionClick: (key: String, TimelineItem.Event) -> Unit,
onReactionLongClick: (key: String, TimelineItem.Event) -> Unit,
@ -323,9 +320,9 @@ private fun MessagesViewContent(
) {
Box(
modifier = modifier
.fillMaxSize()
.navigationBarsPadding()
.imePadding(),
.fillMaxSize()
.navigationBarsPadding()
.imePadding(),
) {
AttachmentsBottomSheet(
state = state.composerState,
@ -417,7 +414,9 @@ private fun MessagesViewContent(
MessagesViewComposerBottomSheetContents(
subcomposing = subcomposing,
state = state,
onRoomSuccessorClicked = onRoomSuccessorClicked,
onRoomSuccessorClick = { roomId ->
state.timelineState.eventSink(TimelineEvents.NavigateToRoom(roomId = roomId))
},
onLinkClick = { url, customTab -> onLinkClick(Link(url), customTab) },
)
},
@ -432,12 +431,12 @@ private fun MessagesViewContent(
private fun MessagesViewComposerBottomSheetContents(
subcomposing: Boolean,
state: MessagesState,
onRoomSuccessorClicked: (RoomId) -> Unit,
onRoomSuccessorClick: (RoomId) -> Unit,
onLinkClick: (String, Boolean) -> Unit,
) {
when {
state.successorRoom != null -> {
SuccessorRoomBanner(roomSuccessor = state.successorRoom, onRoomSuccessorClicked = onRoomSuccessorClicked)
SuccessorRoomBanner(roomSuccessor = state.successorRoom, onRoomSuccessorClick = onRoomSuccessorClick)
}
state.userEventPermissions.canSendMessage -> {
Column(modifier = Modifier.fillMaxWidth()) {
@ -508,8 +507,8 @@ private fun MessagesViewTopBar(
val roundedCornerShape = RoundedCornerShape(8.dp)
Row(
modifier = Modifier
.clip(roundedCornerShape)
.clickable { onRoomDetailsClick() },
.clip(roundedCornerShape)
.clickable { onRoomDetailsClick() },
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
@ -605,17 +604,17 @@ private fun CantSendMessageBanner() {
@Composable
private fun SuccessorRoomBanner(
modifier: Modifier = Modifier,
roomSuccessor: SuccessorRoom,
onRoomSuccessorClicked: (RoomId) -> Unit
onRoomSuccessorClick: (RoomId) -> Unit,
modifier: Modifier = Modifier,
) {
ComposerAlertMolecule(
avatar = null,
content = "This room has been replaced and is no longer active".toAnnotatedString(),
onSubmitClick = { onRoomSuccessorClicked(roomSuccessor.roomId)},
content = stringResource(R.string.screen_room_timeline_tombstoned_room_message).toAnnotatedString(),
onSubmitClick = { onRoomSuccessorClick(roomSuccessor.roomId) },
modifier = modifier,
isCritical = false,
submitText = "Jump to new room"
submitText = stringResource(R.string.screen_room_timeline_tombstoned_room_action)
)
}
@ -628,7 +627,6 @@ internal fun MessagesViewPreview(@PreviewParameter(MessagesStateProvider::class)
onRoomDetailsClick = {},
onEventContentClick = { _, _ -> false },
onUserDataClick = {},
onRoomDataClick = { },
onLinkClick = { _, _ -> },
onSendLocationClick = {},
onCreatePollClick = {},

View file

@ -40,7 +40,6 @@ internal fun MessagesViewWithIdentityChangePreview(
onCreatePollClick = {},
onJoinCallClick = {},
onViewAllPinnedMessagesClick = {},
onRoomDataClick = {},
knockRequestsBannerView = {}
)
}

View file

@ -9,6 +9,7 @@ package io.element.android.features.messages.impl.timeline
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
import kotlin.time.Duration
@ -30,6 +31,7 @@ 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
/**
* Events coming from a poll item.

View file

@ -178,6 +178,9 @@ class TimelinePresenter @AssistedInject constructor(
is TimelineEvents.ComputeVerifiedUserSendFailure -> {
resolveVerifiedUserSendFailureState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(event.event))
}
is TimelineEvents.NavigateToRoom -> {
navigator.onNavigateToRoom(event.roomId)
}
}
}

View file

@ -41,7 +41,15 @@ fun TimelineItemVirtualRow(
when (virtual.model) {
is TimelineItemDaySeparatorModel -> TimelineItemDaySeparatorView(virtual.model)
TimelineItemReadMarkerModel -> TimelineItemReadMarkerView()
TimelineItemRoomBeginningModel -> TimelineItemRoomBeginningView(predecessorRoom = timelineRoomInfo.predecessorRoom, roomName = timelineRoomInfo.name)
TimelineItemRoomBeginningModel -> {
TimelineItemRoomBeginningView(
predecessorRoom = timelineRoomInfo.predecessorRoom,
roomName = timelineRoomInfo.name,
onPredecessorRoomClick = { roomId ->
eventSink(TimelineEvents.NavigateToRoom(roomId))
},
)
}
is TimelineItemLoadingIndicatorModel -> {
TimelineLoadingMoreIndicator(virtual.model.direction)
val latestEventSink by rememberUpdatedState(eventSink)

View file

@ -31,28 +31,28 @@ import io.element.android.libraries.matrix.api.room.tombstone.PredecessorRoom
@Composable
fun TimelineItemRoomBeginningView(
predecessorRoom: PredecessorRoom?,
roomName: String?,
predecessorRoom: PredecessorRoom?,
onPredecessorRoomClick: (RoomId) -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier =modifier.fillMaxWidth()
modifier = modifier.fillMaxWidth()
) {
if(predecessorRoom != null) {
if (predecessorRoom != null) {
ComposerAlertMolecule(
avatar = null,
content = "This room is a continuation of another room".toAnnotatedString(),
onSubmitClick = { },
modifier = modifier,
content = stringResource(R.string.screen_room_timeline_upgraded_room_message).toAnnotatedString(),
onSubmitClick = { onPredecessorRoomClick(predecessorRoom.roomId) },
isCritical = false,
submitText = "See old messages"
submitText = stringResource(R.string.screen_room_timeline_upgraded_room_action)
)
}
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
contentAlignment = Alignment.Center,
) {
val text = if (roomName == null) {
@ -70,22 +70,24 @@ fun TimelineItemRoomBeginningView(
}
}
@PreviewsDayNight
@Composable
internal fun TimelineItemRoomBeginningViewPreview() = ElementPreview {
Column(verticalArrangement = spacedBy(16.dp)){
Column(verticalArrangement = spacedBy(16.dp)) {
TimelineItemRoomBeginningView(
predecessorRoom = null,
roomName = null,
onPredecessorRoomClick = {},
)
TimelineItemRoomBeginningView(
predecessorRoom = null,
roomName = "Room Name",
onPredecessorRoomClick = {},
)
TimelineItemRoomBeginningView(
predecessorRoom = PredecessorRoom(RoomId("!roomId:matrix.org"),EventId("\$eventId:matrix.org") ),
predecessorRoom = PredecessorRoom(RoomId("!roomId:matrix.org"), EventId("\$eventId:matrix.org")),
roomName = "Room Name",
onPredecessorRoomClick = {},
)
}
}

View file

@ -26,8 +26,6 @@ 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.components.avatar.anAvatarData
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.text.toAnnotatedString
@ -71,7 +69,7 @@ fun ComposerAlertMolecule(
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
if(avatar != null) {
if (avatar != null) {
Avatar(avatarData = avatar)
}
Text(

View file

@ -237,6 +237,7 @@ interface BaseRoom : Closeable {
* @param reason - The reason the room is being reported.
*/
suspend fun reportRoom(reason: String?): Result<Unit>
/**
* Destroy the room and release all resources associated to it.
*/

View file

@ -14,7 +14,6 @@ import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.RoomInfo
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.impl.room.history.map
import io.element.android.libraries.matrix.impl.room.join.map
@ -29,7 +28,6 @@ import uniffi.matrix_sdk_base.EncryptionState
import org.matrix.rustcomponents.sdk.Membership as RustMembership
import org.matrix.rustcomponents.sdk.RoomInfo as RustRoomInfo
import org.matrix.rustcomponents.sdk.RoomNotificationMode as RustRoomNotificationMode
import org.matrix.rustcomponents.sdk.SuccessorRoom as RustSuccessorRoom
class RoomInfoMapper {
fun map(rustRoomInfo: RustRoomInfo): RoomInfo = rustRoomInfo.let {

View file

@ -15,9 +15,9 @@ import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.RoomInfo
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
import io.element.android.libraries.matrix.api.room.join.JoinRule
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
import io.element.android.libraries.matrix.test.A_ROOM_ID

View file

@ -15,10 +15,10 @@ import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.RoomInfo
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
import io.element.android.libraries.matrix.api.room.join.JoinRule
import io.element.android.libraries.matrix.api.room.message.RoomMessage
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import io.element.android.libraries.matrix.api.user.MatrixUser