Skip to content

Commit 2bdb3f8

Browse files
authored
Merge pull request #4862 from element-hq/feature/fga/room-version-upgrade
Feature : room version upgrade
2 parents 564beb3 + e2b1ab2 commit 2bdb3f8

File tree

85 files changed

+494
-138
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

85 files changed

+494
-138
lines changed

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNavigator.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package io.element.android.features.messages.impl
99

1010
import io.element.android.features.messages.impl.attachments.Attachment
1111
import io.element.android.libraries.matrix.api.core.EventId
12+
import io.element.android.libraries.matrix.api.core.RoomId
1213
import io.element.android.libraries.matrix.api.core.UserId
1314
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
1415
import kotlinx.collections.immutable.ImmutableList
@@ -19,4 +20,5 @@ interface MessagesNavigator {
1920
fun onReportContentClick(eventId: EventId, senderId: UserId)
2021
fun onEditPollClick(eventId: EventId)
2122
fun onPreviewAttachment(attachments: ImmutableList<Attachment>)
23+
fun onNavigateToRoom(roomId: RoomId)
2224
}

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,21 @@ import io.element.android.libraries.architecture.NodeInputs
4949
import io.element.android.libraries.architecture.inputs
5050
import io.element.android.libraries.core.bool.orFalse
5151
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
52+
import io.element.android.libraries.di.ApplicationContext
5253
import io.element.android.libraries.di.RoomScope
5354
import io.element.android.libraries.di.annotations.SessionCoroutineScope
5455
import io.element.android.libraries.matrix.api.analytics.toAnalyticsViewRoom
5556
import io.element.android.libraries.matrix.api.core.EventId
5657
import io.element.android.libraries.matrix.api.core.RoomId
5758
import io.element.android.libraries.matrix.api.core.UserId
59+
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
5860
import io.element.android.libraries.matrix.api.permalink.PermalinkData
5961
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
6062
import io.element.android.libraries.matrix.api.room.BaseRoom
6163
import io.element.android.libraries.matrix.api.room.alias.matches
6264
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
6365
import io.element.android.libraries.mediaplayer.api.MediaPlayer
66+
import io.element.android.libraries.ui.strings.CommonStrings
6467
import io.element.android.services.analytics.api.AnalyticsService
6568
import kotlinx.collections.immutable.ImmutableList
6669
import kotlinx.coroutines.CoroutineScope
@@ -70,6 +73,7 @@ import kotlinx.coroutines.launch
7073
class MessagesNode @AssistedInject constructor(
7174
@Assisted buildContext: BuildContext,
7275
@Assisted plugins: List<Plugin>,
76+
@ApplicationContext private val context: Context,
7377
@SessionCoroutineScope
7478
private val sessionCoroutineScope: CoroutineScope,
7579
private val room: BaseRoom,
@@ -157,7 +161,7 @@ class MessagesNode @AssistedInject constructor(
157161
callbacks.forEach { it.onUserDataClick(permalink.userId) }
158162
}
159163
is PermalinkData.RoomLink -> {
160-
handleRoomLinkClick(activity, permalink, eventSink)
164+
handleRoomLinkClick(permalink, eventSink)
161165
}
162166
is PermalinkData.FallbackLink -> {
163167
if (customTab) {
@@ -173,7 +177,6 @@ class MessagesNode @AssistedInject constructor(
173177
}
174178

175179
private fun handleRoomLinkClick(
176-
context: Context,
177180
roomLink: PermalinkData.RoomLink,
178181
eventSink: (TimelineEvents) -> Unit,
179182
) {
@@ -183,7 +186,7 @@ class MessagesNode @AssistedInject constructor(
183186
eventSink(TimelineEvents.FocusOnEvent(eventId))
184187
} else {
185188
// Click on the same room, ignore
186-
context.toast("Already viewing this room!")
189+
displaySameRoomToast()
187190
}
188191
} else {
189192
callbacks.forEach { it.onPermalinkClick(roomLink) }
@@ -210,6 +213,15 @@ class MessagesNode @AssistedInject constructor(
210213
callbacks.forEach { it.onPreviewAttachments(attachments) }
211214
}
212215

216+
override fun onNavigateToRoom(roomId: RoomId) {
217+
if (roomId == room.roomId) {
218+
displaySameRoomToast()
219+
} else {
220+
val permalinkData = PermalinkData.RoomLink(roomId.toRoomIdOrAlias())
221+
callbacks.forEach { it.onPermalinkClick(permalinkData) }
222+
}
223+
}
224+
213225
private fun onViewAllPinnedMessagesClick() {
214226
callbacks.forEach { it.onViewAllPinnedEvents() }
215227
}
@@ -230,6 +242,10 @@ class MessagesNode @AssistedInject constructor(
230242
callbacks.forEach { it.onViewKnockRequests() }
231243
}
232244

245+
private fun displaySameRoomToast() {
246+
context.toast(CommonStrings.screen_room_permalink_same_room_android)
247+
}
248+
233249
@Composable
234250
override fun View(modifier: Modifier) {
235251
val activity = requireNotNull(LocalActivity.current)
@@ -255,13 +271,13 @@ class MessagesNode @AssistedInject constructor(
255271
onCreatePollClick = this::onCreatePollClick,
256272
onJoinCallClick = this::onJoinCallClick,
257273
onViewAllPinnedMessagesClick = this::onViewAllPinnedMessagesClick,
274+
modifier = modifier,
258275
knockRequestsBannerView = {
259276
knockRequestsBannerRenderer.View(
260277
modifier = Modifier,
261278
onViewRequestsClick = this::onViewKnockRequestsClick
262279
)
263280
},
264-
modifier = modifier,
265281
)
266282
roomMemberModerationRenderer.Render(
267283
state = state.roomMemberModerationState,

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ class MessagesPresenter @AssistedInject constructor(
270270
pinnedMessagesBannerState = pinnedMessagesBannerState,
271271
dmUserVerificationState = dmUserVerificationState,
272272
roomMemberModerationState = roomMemberModerationState,
273+
successorRoom = roomInfo.successorRoom,
273274
eventSink = { handleEvents(it) }
274275
)
275276
}

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData
2626
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
2727
import io.element.android.libraries.matrix.api.core.RoomId
2828
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
29+
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
2930
import kotlinx.collections.immutable.ImmutableList
3031

3132
@Immutable
@@ -56,5 +57,6 @@ data class MessagesState(
5657
val pinnedMessagesBannerState: PinnedMessagesBannerState,
5758
val dmUserVerificationState: IdentityState?,
5859
val roomMemberModerationState: RoomMemberModerationState,
60+
val successorRoom: SuccessorRoom?,
5961
val eventSink: (MessagesEvents) -> Unit
6062
)

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData
4444
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
4545
import io.element.android.libraries.matrix.api.core.RoomId
4646
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
47+
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
4748
import io.element.android.libraries.textcomposer.model.MessageComposerMode
4849
import io.element.android.libraries.textcomposer.model.aTextEditorStateRich
4950
import kotlinx.collections.immutable.persistentListOf
@@ -87,6 +88,7 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
8788
),
8889
aMessagesState(roomName = AsyncData.Success("A DM with a very looong name"), dmUserVerificationState = IdentityState.Verified),
8990
aMessagesState(roomName = AsyncData.Success("A DM with a very looong name"), dmUserVerificationState = IdentityState.VerificationViolation),
91+
aMessagesState(successorRoom = SuccessorRoom(RoomId("!id:domain"), null)),
9092
)
9193
}
9294

@@ -119,6 +121,7 @@ fun aMessagesState(
119121
pinnedMessagesBannerState: PinnedMessagesBannerState = aLoadedPinnedMessagesBannerState(),
120122
dmUserVerificationState: IdentityState? = null,
121123
roomMemberModerationState: RoomMemberModerationState = aRoomMemberModerationState(),
124+
successorRoom: SuccessorRoom? = null,
122125
eventSink: (MessagesEvents) -> Unit = {},
123126
) = MessagesState(
124127
roomId = RoomId("!id:domain"),
@@ -147,6 +150,7 @@ fun aMessagesState(
147150
pinnedMessagesBannerState = pinnedMessagesBannerState,
148151
dmUserVerificationState = dmUserVerificationState,
149152
roomMemberModerationState = roomMemberModerationState,
153+
successorRoom = successorRoom,
150154
eventSink = eventSink,
151155
)
152156

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt

Lines changed: 76 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ import io.element.android.features.messages.impl.voicemessages.composer.VoiceMes
8282
import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorView
8383
import io.element.android.features.roomcall.api.RoomCallState
8484
import io.element.android.libraries.androidutils.ui.hideKeyboard
85+
import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertMolecule
8586
import io.element.android.libraries.designsystem.atomic.molecules.IconTitlePlaceholdersRowMolecule
8687
import io.element.android.libraries.designsystem.components.avatar.AvatarData
8788
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
@@ -90,6 +91,7 @@ import io.element.android.libraries.designsystem.components.button.BackButton
9091
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
9192
import io.element.android.libraries.designsystem.preview.ElementPreview
9293
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
94+
import io.element.android.libraries.designsystem.text.toAnnotatedString
9395
import io.element.android.libraries.designsystem.theme.components.BottomSheetDragHandle
9496
import io.element.android.libraries.designsystem.theme.components.Icon
9597
import io.element.android.libraries.designsystem.theme.components.Scaffold
@@ -101,8 +103,10 @@ import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
101103
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost
102104
import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState
103105
import io.element.android.libraries.matrix.api.core.EventId
106+
import io.element.android.libraries.matrix.api.core.RoomId
104107
import io.element.android.libraries.matrix.api.core.UserId
105108
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
109+
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
106110
import io.element.android.libraries.matrix.api.user.MatrixUser
107111
import io.element.android.libraries.textcomposer.model.TextEditorState
108112
import io.element.android.libraries.ui.strings.CommonStrings
@@ -211,8 +215,8 @@ fun MessagesView(
211215
onMessageLongClick = ::onMessageLongClick,
212216
onUserDataClick = {
213217
hidingKeyboard {
214-
state.eventSink(MessagesEvents.OnUserClicked(it))
215-
}
218+
state.eventSink(MessagesEvents.OnUserClicked(it))
219+
}
216220
},
217221
onLinkClick = { link, customTab ->
218222
if (customTab) {
@@ -410,6 +414,9 @@ private fun MessagesViewContent(
410414
MessagesViewComposerBottomSheetContents(
411415
subcomposing = subcomposing,
412416
state = state,
417+
onRoomSuccessorClick = { roomId ->
418+
state.timelineState.eventSink(TimelineEvents.NavigateToRoom(roomId = roomId))
419+
},
413420
onLinkClick = { url, customTab -> onLinkClick(Link(url), customTab) },
414421
)
415422
},
@@ -424,52 +431,59 @@ private fun MessagesViewContent(
424431
private fun MessagesViewComposerBottomSheetContents(
425432
subcomposing: Boolean,
426433
state: MessagesState,
434+
onRoomSuccessorClick: (RoomId) -> Unit,
427435
onLinkClick: (String, Boolean) -> Unit,
428436
) {
429-
if (state.userEventPermissions.canSendMessage) {
430-
Column(modifier = Modifier.fillMaxWidth()) {
431-
SuggestionsPickerView(
432-
modifier = Modifier
433-
.heightIn(max = 230.dp)
434-
// Consume all scrolling, preventing the bottom sheet from being dragged when interacting with the list of suggestions
435-
.nestedScroll(object : NestedScrollConnection {
436-
override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset {
437-
return available
438-
}
439-
}),
440-
roomId = state.roomId,
441-
roomName = state.roomName.dataOrNull(),
442-
roomAvatarData = state.roomAvatar.dataOrNull(),
443-
suggestions = state.composerState.suggestions,
444-
onSelectSuggestion = {
445-
state.composerState.eventSink(MessageComposerEvents.InsertSuggestion(it))
446-
}
447-
)
448-
// Do not show the identity change if user is composing a Rich message or is seeing suggestion(s).
449-
if (state.composerState.suggestions.isEmpty() &&
450-
state.composerState.textEditorState is TextEditorState.Markdown) {
451-
IdentityChangeStateView(
452-
state = state.identityChangeState,
453-
onLinkClick = onLinkClick,
454-
)
455-
}
456-
val verificationViolation = state.identityChangeState.roomMemberIdentityStateChanges.firstOrNull {
457-
it.identityState == IdentityState.VerificationViolation
458-
}
459-
if (verificationViolation != null) {
460-
DisabledComposerView(modifier = Modifier.fillMaxWidth())
461-
} else {
462-
MessageComposerView(
463-
state = state.composerState,
464-
voiceMessageState = state.voiceMessageComposerState,
465-
subcomposing = subcomposing,
466-
enableVoiceMessages = state.enableVoiceMessages,
467-
modifier = Modifier.fillMaxWidth(),
437+
when {
438+
state.successorRoom != null -> {
439+
SuccessorRoomBanner(roomSuccessor = state.successorRoom, onRoomSuccessorClick = onRoomSuccessorClick)
440+
}
441+
state.userEventPermissions.canSendMessage -> {
442+
Column(modifier = Modifier.fillMaxWidth()) {
443+
SuggestionsPickerView(
444+
modifier = Modifier
445+
.heightIn(max = 230.dp)
446+
// Consume all scrolling, preventing the bottom sheet from being dragged when interacting with the list of suggestions
447+
.nestedScroll(object : NestedScrollConnection {
448+
override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset {
449+
return available
450+
}
451+
}),
452+
roomId = state.roomId,
453+
roomName = state.roomName.dataOrNull(),
454+
roomAvatarData = state.roomAvatar.dataOrNull(),
455+
suggestions = state.composerState.suggestions,
456+
onSelectSuggestion = {
457+
state.composerState.eventSink(MessageComposerEvents.InsertSuggestion(it))
458+
}
468459
)
460+
// Do not show the identity change if user is composing a Rich message or is seeing suggestion(s).
461+
if (state.composerState.suggestions.isEmpty() &&
462+
state.composerState.textEditorState is TextEditorState.Markdown) {
463+
IdentityChangeStateView(
464+
state = state.identityChangeState,
465+
onLinkClick = onLinkClick,
466+
)
467+
}
468+
val verificationViolation = state.identityChangeState.roomMemberIdentityStateChanges.firstOrNull {
469+
it.identityState == IdentityState.VerificationViolation
470+
}
471+
if (verificationViolation != null) {
472+
DisabledComposerView(modifier = Modifier.fillMaxWidth())
473+
} else {
474+
MessageComposerView(
475+
state = state.composerState,
476+
voiceMessageState = state.voiceMessageComposerState,
477+
subcomposing = subcomposing,
478+
enableVoiceMessages = state.enableVoiceMessages,
479+
modifier = Modifier.fillMaxWidth(),
480+
)
481+
}
469482
}
470483
}
471-
} else {
472-
CantSendMessageBanner()
484+
else -> {
485+
CantSendMessageBanner()
486+
}
473487
}
474488
}
475489

@@ -572,9 +586,9 @@ private fun RoomAvatarAndNameRow(
572586
private fun CantSendMessageBanner() {
573587
Row(
574588
modifier = Modifier
575-
.fillMaxWidth()
576-
.background(ElementTheme.colors.bgSubtleSecondary)
577-
.padding(16.dp),
589+
.fillMaxWidth()
590+
.background(ElementTheme.colors.bgSubtleSecondary)
591+
.padding(16.dp),
578592
verticalAlignment = Alignment.CenterVertically,
579593
horizontalArrangement = Arrangement.Center
580594
) {
@@ -588,6 +602,22 @@ private fun CantSendMessageBanner() {
588602
}
589603
}
590604

605+
@Composable
606+
private fun SuccessorRoomBanner(
607+
roomSuccessor: SuccessorRoom,
608+
onRoomSuccessorClick: (RoomId) -> Unit,
609+
modifier: Modifier = Modifier,
610+
) {
611+
ComposerAlertMolecule(
612+
avatar = null,
613+
content = stringResource(R.string.screen_room_timeline_tombstoned_room_message).toAnnotatedString(),
614+
onSubmitClick = { onRoomSuccessorClick(roomSuccessor.roomId) },
615+
modifier = modifier,
616+
isCritical = false,
617+
submitText = stringResource(R.string.screen_room_timeline_tombstoned_room_action)
618+
)
619+
}
620+
591621
@PreviewsDayNight
592622
@Composable
593623
internal fun MessagesViewPreview(@PreviewParameter(MessagesStateProvider::class) state: MessagesState) = ElementPreview {

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ class PinnedMessagesListPresenter @AssistedInject constructor(
106106
renderTypingNotifications = false,
107107
typingMembers = persistentListOf(),
108108
reserveSpace = false,
109-
)
109+
),
110+
predecessorRoom = room.predecessorRoom(),
110111
)
111112
}
112113
val timelineProtectionState = timelineProtectionPresenter.present()

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package io.element.android.features.messages.impl.timeline
99

1010
import io.element.android.features.messages.impl.timeline.model.TimelineItem
1111
import io.element.android.libraries.matrix.api.core.EventId
12+
import io.element.android.libraries.matrix.api.core.RoomId
1213
import io.element.android.libraries.matrix.api.timeline.Timeline
1314
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
1415
import kotlin.time.Duration
@@ -30,6 +31,7 @@ sealed interface TimelineEvents {
3031
data class ComputeVerifiedUserSendFailure(val event: TimelineItem.Event) : EventFromTimelineItem
3132
data class ShowShieldDialog(val messageShield: MessageShield) : EventFromTimelineItem
3233
data class LoadMore(val direction: Timeline.PaginationDirection) : EventFromTimelineItem
34+
data class NavigateToRoom(val roomId: RoomId) : EventFromTimelineItem
3335

3436
/**
3537
* Events coming from a poll item.

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ class TimelinePresenter @AssistedInject constructor(
178178
is TimelineEvents.ComputeVerifiedUserSendFailure -> {
179179
resolveVerifiedUserSendFailureState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(event.event))
180180
}
181+
is TimelineEvents.NavigateToRoom -> {
182+
navigator.onNavigateToRoom(event.roomId)
183+
}
181184
}
182185
}
183186

@@ -257,8 +260,9 @@ class TimelinePresenter @AssistedInject constructor(
257260
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
258261
userHasPermissionToSendReaction = userHasPermissionToSendReaction,
259262
roomCallState = roomCallState,
260-
pinnedEventIds = roomInfo.pinnedEventIds.orEmpty(),
263+
pinnedEventIds = roomInfo.pinnedEventIds,
261264
typingNotificationState = typingNotificationState,
265+
predecessorRoom = room.predecessorRoom(),
262266
)
263267
}
264268
}

0 commit comments

Comments
 (0)