Skip to content

Commit 9c43617

Browse files
committed
tombstoned and upgraded rooms implementation
1 parent 143fd9e commit 9c43617

29 files changed

+301
-89
lines changed

ElementX.xcodeproj/project.pbxproj

Lines changed: 22 additions & 31 deletions
Large diffs are not rendered by default.

ElementX/Resources/Localizations/en.lproj/Localizable.strings

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,11 @@
515515
"screen_room_details_pinned_events_row_title" = "Pinned messages";
516516
"screen_room_details_profile_row_title" = "Profile";
517517
"screen_room_details_requests_to_join_title" = "Requests to join";
518+
"screen_room_timeline_tombstoned_room_action" = "Jump to new room";
519+
"screen_room_timeline_tombstoned_room_message" = "This room has been replaced and is no longer active";
520+
"screen_room_timeline_upgraded_room_action" = "See old messages";
521+
"screen_room_timeline_upgraded_room_message" = "This room is a continuation of another room";
522+
"screen_roomlist_tombstoned_room_description" = "This room has been upgraded";
518523
"screen_security_and_privacy_add_room_address_action" = "Add room address";
519524
"screen_security_and_privacy_ask_to_join_option_description" = "Anyone can ask to join the room but an administrator or moderator will have to accept the request.";
520525
"screen_security_and_privacy_enable_encryption_alert_confirm_button_title" = "Yes, enable encryption";

ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
549549
stateMachine.tryEvent(.presentKnockRequestsListScreen)
550550
case .presentThread(let itemID):
551551
stateMachine.tryEvent(.presentThread(itemID: itemID))
552+
case .presentRoom(roomID: let roomID):
553+
stateMachine.tryEvent(.startChildFlow(roomID: roomID, via: [], entryPoint: .room))
552554
}
553555
}
554556
.store(in: &cancellables)

ElementX/Sources/Generated/Strings.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2312,6 +2312,14 @@ internal enum L10n {
23122312
internal static func screenRoomTimelineStateChanges(_ p1: Int) -> String {
23132313
return L10n.tr("Localizable", "screen_room_timeline_state_changes", p1)
23142314
}
2315+
/// Jump to new room
2316+
internal static var screenRoomTimelineTombstonedRoomAction: String { return L10n.tr("Localizable", "screen_room_timeline_tombstoned_room_action") }
2317+
/// This room has been replaced and is no longer active
2318+
internal static var screenRoomTimelineTombstonedRoomMessage: String { return L10n.tr("Localizable", "screen_room_timeline_tombstoned_room_message") }
2319+
/// See old messages
2320+
internal static var screenRoomTimelineUpgradedRoomAction: String { return L10n.tr("Localizable", "screen_room_timeline_upgraded_room_action") }
2321+
/// This room is a continuation of another room
2322+
internal static var screenRoomTimelineUpgradedRoomMessage: String { return L10n.tr("Localizable", "screen_room_timeline_upgraded_room_message") }
23152323
/// Chat
23162324
internal static var screenRoomTitle: String { return L10n.tr("Localizable", "screen_room_title") }
23172325
/// Plural format key: "%#@COUNT@"
@@ -2380,6 +2388,8 @@ internal enum L10n {
23802388
internal static var screenRoomlistMarkAsRead: String { return L10n.tr("Localizable", "screen_roomlist_mark_as_read") }
23812389
/// Mark as unread
23822390
internal static var screenRoomlistMarkAsUnread: String { return L10n.tr("Localizable", "screen_roomlist_mark_as_unread") }
2391+
/// This room has been upgraded
2392+
internal static var screenRoomlistTombstonedRoomDescription: String { return L10n.tr("Localizable", "screen_roomlist_tombstoned_room_description") }
23832393
/// Add room address
23842394
internal static var screenSecurityAndPrivacyAddRoomAddressAction: String { return L10n.tr("Localizable", "screen_security_and_privacy_add_room_address_action") }
23852395
/// Anyone can ask to join the room but an administrator or moderator will have to accept the request.

ElementX/Sources/Mocks/Generated/GeneratedMocks.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6484,6 +6484,7 @@ class JoinedRoomProxyMock: JoinedRoomProxyProtocol, @unchecked Sendable {
64846484
set(value) { underlyingTimeline = value }
64856485
}
64866486
var underlyingTimeline: TimelineProxyProtocol!
6487+
var predecessorRoom: PredecessorRoom?
64876488
var id: String {
64886489
get { return underlyingId }
64896490
set(value) { underlyingId = value }

ElementX/Sources/Mocks/JoinedRoomProxyMock.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ struct JoinedRoomProxyMockConfiguration {
4848
var membership: Membership = .joined
4949

5050
var isVisibleInPublicDirectory = false
51+
var predecessor: PredecessorRoom?
52+
var successor: SuccessorRoom?
5153
}
5254

5355
extension JoinedRoomProxyMock {
@@ -137,6 +139,8 @@ extension JoinedRoomProxyMock {
137139
clearDraftReturnValue = .success(())
138140
sendTypingNotificationIsTypingReturnValue = .success(())
139141
isVisibleInRoomDirectoryReturnValue = .success(configuration.isVisibleInPublicDirectory)
142+
143+
predecessorRoom = configuration.predecessor
140144
}
141145
}
142146

@@ -152,7 +156,7 @@ extension RoomInfo {
152156
isDirect: configuration.isDirect,
153157
isPublic: configuration.isPublic,
154158
isSpace: configuration.isSpace,
155-
successorRoom: nil,
159+
successorRoom: configuration.successor,
156160
isFavourite: false,
157161
canonicalAlias: configuration.canonicalAlias,
158162
alternativeAliases: configuration.alternativeAliases,

ElementX/Sources/Mocks/RoomSummaryProviderMock.swift

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ extension RoomSummary {
8989
alternativeAliases: [],
9090
hasOngoingCall: false,
9191
isMarkedUnread: false,
92-
isFavourite: false)
92+
isFavourite: false,
93+
isTombstoned: false)
9394
}
9495
}
9596

@@ -113,7 +114,8 @@ extension Array where Element == RoomSummary {
113114
alternativeAliases: [],
114115
hasOngoingCall: false,
115116
isMarkedUnread: false,
116-
isFavourite: false),
117+
isFavourite: false,
118+
isTombstoned: false),
117119
RoomSummary(room: RoomSDKMock(),
118120
id: "2",
119121
joinRequestType: nil,
@@ -132,7 +134,8 @@ extension Array where Element == RoomSummary {
132134
alternativeAliases: [],
133135
hasOngoingCall: false,
134136
isMarkedUnread: false,
135-
isFavourite: false),
137+
isFavourite: false,
138+
isTombstoned: false),
136139
RoomSummary(room: RoomSDKMock(),
137140
id: "3",
138141
joinRequestType: nil,
@@ -151,7 +154,8 @@ extension Array where Element == RoomSummary {
151154
alternativeAliases: [],
152155
hasOngoingCall: false,
153156
isMarkedUnread: false,
154-
isFavourite: false),
157+
isFavourite: false,
158+
isTombstoned: false),
155159
RoomSummary(room: RoomSDKMock(),
156160
id: "4",
157161
joinRequestType: nil,
@@ -170,7 +174,8 @@ extension Array where Element == RoomSummary {
170174
alternativeAliases: [],
171175
hasOngoingCall: false,
172176
isMarkedUnread: false,
173-
isFavourite: false),
177+
isFavourite: false,
178+
isTombstoned: false),
174179
RoomSummary(room: RoomSDKMock(),
175180
id: "5",
176181
joinRequestType: nil,
@@ -189,7 +194,8 @@ extension Array where Element == RoomSummary {
189194
alternativeAliases: [],
190195
hasOngoingCall: true,
191196
isMarkedUnread: false,
192-
isFavourite: false),
197+
isFavourite: false,
198+
isTombstoned: false),
193199
RoomSummary(room: RoomSDKMock(),
194200
id: "6",
195201
joinRequestType: nil,
@@ -208,7 +214,28 @@ extension Array where Element == RoomSummary {
208214
alternativeAliases: [],
209215
hasOngoingCall: true,
210216
isMarkedUnread: false,
211-
isFavourite: false),
217+
isFavourite: false,
218+
isTombstoned: false),
219+
RoomSummary(room: RoomSDKMock(),
220+
id: "7",
221+
joinRequestType: nil,
222+
name: "Tombstoned",
223+
isDirect: false,
224+
avatarURL: nil,
225+
heroes: [],
226+
activeMembersCount: 0,
227+
lastMessage: nil,
228+
lastMessageDate: .mock,
229+
unreadMessagesCount: 1,
230+
unreadMentionsCount: 0,
231+
unreadNotificationsCount: 1,
232+
notificationMode: .allMessages,
233+
canonicalAlias: nil,
234+
alternativeAliases: [],
235+
hasOngoingCall: false,
236+
isMarkedUnread: false,
237+
isFavourite: false,
238+
isTombstoned: true),
212239
RoomSummary(room: RoomSDKMock(),
213240
id: "0",
214241
joinRequestType: nil,
@@ -227,7 +254,8 @@ extension Array where Element == RoomSummary {
227254
alternativeAliases: [],
228255
hasOngoingCall: false,
229256
isMarkedUnread: false,
230-
isFavourite: false)
257+
isFavourite: false,
258+
isTombstoned: false)
231259
]
232260

233261
static let mockRoomsWithNotificationsState: [Element] = {
@@ -279,7 +307,8 @@ extension Array where Element == RoomSummary {
279307
alternativeAliases: [],
280308
hasOngoingCall: false,
281309
isMarkedUnread: false,
282-
isFavourite: false),
310+
isFavourite: false,
311+
isTombstoned: false),
283312
RoomSummary(room: RoomSDKMock(),
284313
id: "someAwesomeRoomId2",
285314
joinRequestType: .invite(inviter: RoomMemberProxyMock.mockCharlie),
@@ -298,6 +327,7 @@ extension Array where Element == RoomSummary {
298327
alternativeAliases: [],
299328
hasOngoingCall: false,
300329
isMarkedUnread: false,
301-
isFavourite: false)
330+
isFavourite: false,
331+
isTombstoned: false)
302332
]
303333
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// Copyright 2025 New Vector Ltd.
3+
//
4+
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5+
// Please see LICENSE files in the repository root for full details.
6+
//
7+
8+
import Compound
9+
import SwiftUI
10+
11+
struct Highlight: ViewModifier {
12+
let borderColor: Color
13+
let primaryColor: Color
14+
let secondaryColor: Color
15+
16+
func body(content: Content) -> some View {
17+
ZStack(alignment: .top) {
18+
VStack(spacing: 0) {
19+
borderColor
20+
.frame(height: 1)
21+
LinearGradient(colors: [primaryColor, secondaryColor],
22+
startPoint: .top,
23+
endPoint: .bottom)
24+
}
25+
content
26+
.layoutPriority(1)
27+
}
28+
}
29+
}
30+
31+
extension View {
32+
func highlight(borderColor: Color, primaryColor: Color, secondaryColor: Color) -> some View {
33+
modifier(Highlight(borderColor: borderColor, primaryColor: primaryColor, secondaryColor: secondaryColor))
34+
}
35+
}

ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,8 @@ struct HomeScreenRoom: Identifiable, Equatable {
197197

198198
let canonicalAlias: String?
199199

200+
let isTombstoned: Bool
201+
200202
static func placeholder() -> HomeScreenRoom {
201203
HomeScreenRoom(id: UUID().uuidString,
202204
roomID: nil,
@@ -209,7 +211,8 @@ struct HomeScreenRoom: Identifiable, Equatable {
209211
timestamp: "Now",
210212
lastMessage: placeholderLastMessage,
211213
avatar: .room(id: "", name: "", avatarURL: nil),
212-
canonicalAlias: nil)
214+
canonicalAlias: nil,
215+
isTombstoned: false)
213216
}
214217
}
215218

@@ -246,6 +249,7 @@ extension HomeScreenRoom {
246249
timestamp: summary.lastMessageDate?.formattedMinimal(),
247250
lastMessage: summary.lastMessage,
248251
avatar: summary.avatar,
249-
canonicalAlias: summary.canonicalAlias)
252+
canonicalAlias: summary.canonicalAlias,
253+
isTombstoned: summary.isTombstoned)
250254
}
251255
}

ElementX/Sources/Screens/HomeScreen/View/HomeScreenInviteCell.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,8 @@ private extension HomeScreenRoom {
217217
alternativeAliases: [],
218218
hasOngoingCall: false,
219219
isMarkedUnread: false,
220-
isFavourite: false)
220+
isFavourite: false,
221+
isTombstoned: false)
221222

222223
return .init(summary: summary, hideUnreadMessagesBadge: false)
223224
}
@@ -246,7 +247,8 @@ private extension HomeScreenRoom {
246247
alternativeAliases: [],
247248
hasOngoingCall: false,
248249
isMarkedUnread: false,
249-
isFavourite: false)
250+
isFavourite: false,
251+
isTombstoned: false)
250252

251253
return .init(summary: summary, hideUnreadMessagesBadge: false)
252254
}

ElementX/Sources/Screens/HomeScreen/View/HomeScreenKnockedCell.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@ private extension HomeScreenRoom {
162162
alternativeAliases: [],
163163
hasOngoingCall: false,
164164
isMarkedUnread: false,
165-
isFavourite: false)
165+
isFavourite: false,
166+
isTombstoned: false)
166167

167168
return .init(summary: summary, hideUnreadMessagesBadge: false)
168169
}
@@ -191,7 +192,8 @@ private extension HomeScreenRoom {
191192
alternativeAliases: [],
192193
hasOngoingCall: false,
193194
isMarkedUnread: false,
194-
isFavourite: false)
195+
isFavourite: false,
196+
isTombstoned: false)
195197

196198
return .init(summary: summary, hideUnreadMessagesBadge: false)
197199
}

ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,11 @@ struct HomeScreenRoomCell: View {
135135

136136
@ViewBuilder
137137
private var lastMessage: some View {
138-
if let lastMessage = room.lastMessage {
138+
// If the room is tombstoned, show a specific message, regardless of any last message.
139+
if room.isTombstoned {
140+
Text(L10n.screenRoomlistTombstonedRoomDescription)
141+
.lastMessageFormatting()
142+
} else if let lastMessage = room.lastMessage {
139143
Text(lastMessage)
140144
.lastMessageFormatting()
141145
}

ElementX/Sources/Screens/MediaEventsTimelineScreen/MediaEventsTimelineScreenViewModel.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType
8181
case .displayEmojiPicker, .displayReportContent, .displayCameraPicker, .displayMediaPicker,
8282
.displayDocumentPicker, .displayLocationPicker, .displayPollForm, .displayMediaUploadPreviewScreen,
8383
.displaySenderDetails, .displayMessageForwarding, .displayLocation, .displayResolveSendFailure,
84-
.displayThread, .composer, .hasScrolled, .viewInRoomTimeline:
84+
.displayThread, .composer, .hasScrolled, .viewInRoomTimeline, .displayRoom:
8585
break
8686
}
8787
}
@@ -103,7 +103,7 @@ class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType
103103
case .displayEmojiPicker, .displayReportContent, .displayCameraPicker, .displayMediaPicker,
104104
.displayDocumentPicker, .displayLocationPicker, .displayPollForm, .displayMediaUploadPreviewScreen,
105105
.displaySenderDetails, .displayMessageForwarding, .displayLocation, .displayResolveSendFailure,
106-
.displayThread, .composer, .hasScrolled, .viewInRoomTimeline:
106+
.displayThread, .composer, .hasScrolled, .viewInRoomTimeline, .displayRoom:
107107
break
108108
}
109109
}

ElementX/Sources/Screens/PinnedEventsTimelineScreen/PinnedEventsTimelineScreenCoordinator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ final class PinnedEventsTimelineScreenCoordinator: CoordinatorProtocol {
9191
// These other actions will not be handled in this view
9292
case .displayEmojiPicker, .displayReportContent, .displayCameraPicker, .displayMediaPicker,
9393
.displayDocumentPicker, .displayLocationPicker, .displayPollForm, .displayMediaUploadPreviewScreen,
94-
.displayResolveSendFailure, .displayThread, .composer, .hasScrolled:
94+
.displayResolveSendFailure, .displayThread, .composer, .hasScrolled, .displayRoom:
9595
// These actions are not handled in this coordinator
9696
break
9797
}

ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ enum RoomScreenCoordinatorAction {
4545
case presentResolveSendFailure(failure: TimelineItemSendFailure.VerifiedUser, sendHandle: SendHandleProxy)
4646
case presentKnockRequestsList
4747
case presentThread(itemID: TimelineItemIdentifier)
48+
case presentRoom(roomID: String)
4849
}
4950

5051
final class RoomScreenCoordinator: CoordinatorProtocol {
@@ -151,6 +152,8 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
151152
composerViewModel.process(timelineAction: action)
152153
case .hasScrolled(direction: let direction):
153154
roomViewModel.timelineHasScrolled(direction: direction)
155+
case .displayRoom(let roomID):
156+
actionsSubject.send(.presentRoom(roomID: roomID))
154157
case .viewInRoomTimeline:
155158
fatalError("The action: \(action) should not be sent to this coordinator")
156159
}
@@ -182,6 +185,8 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
182185
composerViewModel.process(timelineAction: .removeFocus)
183186
case .displayKnockRequests:
184187
actionsSubject.send(.presentKnockRequestsList)
188+
case .displayRoom(let roomID):
189+
actionsSubject.send(.presentRoom(roomID: roomID))
185190
}
186191
}
187192
.store(in: &cancellables)

ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ enum RoomScreenViewModelAction: Equatable {
1515
case displayCall
1616
case removeComposerFocus
1717
case displayKnockRequests
18+
case displayRoom(roomID: String)
1819
}
1920

2021
enum RoomScreenViewAction {
@@ -26,6 +27,7 @@ enum RoomScreenViewAction {
2627
case acceptKnock(eventID: String)
2728
case dismissKnockRequests
2829
case viewKnockRequests
30+
case displaySuccessorRoom
2931
}
3032

3133
struct RoomScreenViewState: BindableState {
@@ -53,6 +55,8 @@ struct RoomScreenViewState: BindableState {
5355
var unseenKnockRequests: [KnockRequestInfo] = []
5456
var handledEventIDs: Set<String> = []
5557

58+
var hasSuccessor: Bool
59+
5660
var displayedKnockRequests: [KnockRequestInfo] {
5761
unseenKnockRequests.filter { !handledEventIDs.contains($0.eventID) }
5862
}

0 commit comments

Comments
 (0)