diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 74a4d71d8..6b9e0cfff 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -876,7 +876,7 @@ A6FFC4C5154C446BAD6B40D8 /* TimelineItemProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8520AFD6680CBAD388F6D927 /* TimelineItemProvider.swift */; }; A722F426FD81FC67706BB1E0 /* CustomLayoutLabelStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42236480CF0431535EBE8387 /* CustomLayoutLabelStyle.swift */; }; A74438ED16F8683A4B793E6A /* AnalyticsSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BCE3FAF40932AC7C7639AC4 /* AnalyticsSettingsScreenViewModel.swift */; }; - A7CA45942DEF306400300A02 /* Highlight.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7CA45932DEF306200300A02 /* Highlight.swift */; }; + A7CA45942DEF306400300A02 /* HorizontalHighlightGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7CA45932DEF306200300A02 /* HorizontalHighlightGradient.swift */; }; A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; }; A808DC3F72D15C6C5A52317E /* TimelineItemDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCDA016D05107DED3B9495CB /* TimelineItemDebugView.swift */; }; A816F7087C495D85048AC50E /* RoomMemberDetailsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B6E30BB748F3F480F077969 /* RoomMemberDetailsScreenModels.swift */; }; @@ -2185,7 +2185,7 @@ A7978C9EFBDD7DE39BD86726 /* RestorationTokenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestorationTokenTests.swift; sourceTree = ""; }; A7A1B80FE6E3BA72F9C748AD /* AdvancedSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsScreenViewModel.swift; sourceTree = ""; }; A7C4EA55DA62F9D0F984A2AE /* CollapsibleTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleTimelineItem.swift; sourceTree = ""; }; - A7CA45932DEF306200300A02 /* Highlight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Highlight.swift; sourceTree = ""; }; + A7CA45932DEF306200300A02 /* HorizontalHighlightGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HorizontalHighlightGradient.swift; sourceTree = ""; }; A7D452AF7B5F7E3A0A7DB54C /* SessionVerificationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenViewModelProtocol.swift; sourceTree = ""; }; A7E37072597F67C4DD8CC2DB /* ComposerDraftServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerDraftServiceProtocol.swift; sourceTree = ""; }; A84D413BF49F0E980F010A6B /* LogViewerScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewerScreenCoordinator.swift; sourceTree = ""; }; @@ -3329,7 +3329,7 @@ 328DD5DA1281F758B72006C7 /* Views */ = { isa = PBXGroup; children = ( - A7CA45932DEF306200300A02 /* Highlight.swift */, + A7CA45932DEF306200300A02 /* HorizontalHighlightGradient.swift */, 8F21ED7205048668BEB44A38 /* AppActivityView.swift */, CC743C7A85E3171BCBF0A653 /* AvatarHeaderView.swift */, 9A028783CFFF861C5E44FFB1 /* BadgeLabel.swift */, @@ -7637,7 +7637,7 @@ FA71CD334F2D2289BEF0D749 /* SecureBackupRecoveryKeyScreen.swift in Sources */, B1387648C6F71F1B98244803 /* SecureBackupRecoveryKeyScreenCoordinator.swift in Sources */, 8AA84EF202F2EFC8453A97BD /* SecureBackupRecoveryKeyScreenModels.swift in Sources */, - A7CA45942DEF306400300A02 /* Highlight.swift in Sources */, + A7CA45942DEF306400300A02 /* HorizontalHighlightGradient.swift in Sources */, 27F015B0D5436633B5B3C8C3 /* SecureBackupRecoveryKeyScreenViewModel.swift in Sources */, F0570F1ECD70C4C851FB2052 /* SecureBackupRecoveryKeyScreenViewModelProtocol.swift in Sources */, E84ADFE9696936C18C2424B5 /* SecureBackupScreen.swift in Sources */, diff --git a/ElementX/Sources/Other/SwiftUI/Views/Highlight.swift b/ElementX/Sources/Other/SwiftUI/Views/HorizontalHighlightGradient.swift similarity index 82% rename from ElementX/Sources/Other/SwiftUI/Views/Highlight.swift rename to ElementX/Sources/Other/SwiftUI/Views/HorizontalHighlightGradient.swift index 9c1fcbebd..01e3be83c 100644 --- a/ElementX/Sources/Other/SwiftUI/Views/Highlight.swift +++ b/ElementX/Sources/Other/SwiftUI/Views/HorizontalHighlightGradient.swift @@ -8,7 +8,7 @@ import Compound import SwiftUI -struct Highlight: ViewModifier { +struct HorizontalHighlightGradient: ViewModifier { let borderColor: Color let primaryColor: Color let secondaryColor: Color @@ -30,6 +30,6 @@ struct Highlight: ViewModifier { extension View { func highlight(borderColor: Color, primaryColor: Color, secondaryColor: Color) -> some View { - modifier(Highlight(borderColor: borderColor, primaryColor: primaryColor, secondaryColor: secondaryColor)) + modifier(HorizontalHighlightGradient(borderColor: borderColor, primaryColor: primaryColor, secondaryColor: secondaryColor)) } } diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift index 4ad8b6351..730b1ac28 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift @@ -199,6 +199,14 @@ struct HomeScreenRoom: Identifiable, Equatable { let isTombstoned: Bool + var displayedLastMessage: AttributedString? { + // If the room is tombstoned, show a specific message, regardless of any last message. + guard !isTombstoned else { + return AttributedString(L10n.screenRoomlistTombstonedRoomDescription) + } + return lastMessage + } + static func placeholder() -> HomeScreenRoom { HomeScreenRoom(id: UUID().uuidString, roomID: nil, diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift index 0de1093c9..b9751750a 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift @@ -135,12 +135,8 @@ struct HomeScreenRoomCell: View { @ViewBuilder private var lastMessage: some View { - // If the room is tombstoned, show a specific message, regardless of any last message. - if room.isTombstoned { - Text(L10n.screenRoomlistTombstonedRoomDescription) - .lastMessageFormatting() - } else if let lastMessage = room.lastMessage { - Text(lastMessage) + if let displayedLastMessage = room.displayedLastMessage { + Text(displayedLastMessage) .lastMessageFormatting() } } diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index 1c564d281..04e8c4fc2 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -331,6 +331,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol private func handleRoomInfoUpdate(_ roomInfo: RoomInfoProxy) async { state.hasSuccessor = roomInfo.successor != nil + let pinnedEventIDs = roomInfo.pinnedEventIDs // Only update the loading state of the banner if state.pinnedEventsBannerState.isLoading { diff --git a/ElementX/Sources/Screens/Timeline/TimelineModels.swift b/ElementX/Sources/Screens/Timeline/TimelineModels.swift index 2395ce3e9..df8a414e0 100644 --- a/ElementX/Sources/Screens/Timeline/TimelineModels.swift +++ b/ElementX/Sources/Screens/Timeline/TimelineModels.swift @@ -79,7 +79,7 @@ enum TimelineViewAction { case hasScrolled(direction: ScrollDirection) case setOpenURLAction(OpenURLAction) - case seePredecessorTapped + case displayPredecessorRoom } enum TimelineComposerAction { diff --git a/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift b/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift index 3de408818..506817c6a 100644 --- a/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift +++ b/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift @@ -212,8 +212,10 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol { actionsSubject.send(.hasScrolled(direction: direction)) case .setOpenURLAction(let action): state.openURL = action - case .seePredecessorTapped: - guard let predecessorID = roomProxy.predecessorRoom?.roomId else { return } + case .displayPredecessorRoom: + guard let predecessorID = roomProxy.predecessorRoom?.roomId else { + fatalError("Predecessor room should exist if this action is triggered.") + } actionsSubject.send(.displayRoom(roomID: predecessorID)) } } diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/TimelineStartRoomTimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/TimelineStartRoomTimelineView.swift index f986aeae9..c4df20646 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/TimelineStartRoomTimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/TimelineStartRoomTimelineView.swift @@ -15,8 +15,9 @@ struct TimelineStartRoomTimelineView: View { var body: some View { VStack(spacing: 14) { if context?.viewState.hasPredecessor == true { - upgrade + upgradeDialogue } + // We don't display the title for tombstoned DMs if context?.viewState.isDirectOneToOneRoom != true { Text(title) .font(.compound.bodySM) @@ -28,13 +29,13 @@ struct TimelineStartRoomTimelineView: View { } } - private var upgrade: some View { + private var upgradeDialogue: some View { VStack(spacing: 16) { Text(L10n.screenRoomTimelineUpgradedRoomMessage) .font(.compound.bodyMD) .foregroundColor(.compound.textPrimary) Button { - context?.send(viewAction: .seePredecessorTapped) + context?.send(viewAction: .displayPredecessorRoom) } label: { Text(L10n.screenRoomTimelineUpgradedRoomAction) .frame(maxWidth: .infinity) diff --git a/ElementX/Sources/Services/Room/JoinedRoomProxy.swift b/ElementX/Sources/Services/Room/JoinedRoomProxy.swift index e74963012..70ec51a5a 100644 --- a/ElementX/Sources/Services/Room/JoinedRoomProxy.swift +++ b/ElementX/Sources/Services/Room/JoinedRoomProxy.swift @@ -34,7 +34,14 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol { var ownUserID: String { room.ownUserId() } - lazy var predecessorRoom: PredecessorRoom? = room.predecessorRoom() + // The predecessor is set on room creation and never changes, so we lazily store it. + lazy var predecessorRoom = room.predecessorRoom() + + // The successor may change over time, so we access it dynamically. + // It's suggested to observe it through the `infoPublisher` + var successorRoom: SuccessorRoom? { + room.successorRoom() + } let timeline: TimelineProxyProtocol diff --git a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift index fb47183cc..d5a346b68 100644 --- a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift @@ -78,6 +78,8 @@ protocol JoinedRoomProxyProtocol: RoomProxyProtocol { var predecessorRoom: PredecessorRoom? { get } + var successorRoom: SuccessorRoom? { get } + func subscribeForUpdates() async func subscribeToRoomInfoUpdates() diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift index 8a662cf02..46b514317 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift @@ -55,7 +55,6 @@ struct RoomSummary { let isMarkedUnread: Bool let isFavourite: Bool - let isTombstoned: Bool var hasUnreadMessages: Bool { unreadMessagesCount > 0 } diff --git a/ElementX/Sources/Services/Timeline/TimelineController/TimelineController.swift b/ElementX/Sources/Services/Timeline/TimelineController/TimelineController.swift index 744ef67e5..4cc196633 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/TimelineController.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/TimelineController.swift @@ -480,11 +480,12 @@ class TimelineController: TimelineControllerProtocol { case .readMarker: return ReadMarkerRoomTimelineItem(id: .virtual(uniqueID: uniqueID)) case .timelineStart: - if hasPredecessor { + // We always display the timeline start item, if there is a predecessor room. + guard !hasPredecessor else { return TimelineStartRoomTimelineItem(name: roomDisplayName) - } else { - return isDM ? nil : TimelineStartRoomTimelineItem(name: roomDisplayName) } + // If not we only display the timeline start item if this is not a DM. + return isDM ? nil : TimelineStartRoomTimelineItem(name: roomDisplayName) } case .unknown: return nil diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Virtual/TimelineStartRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Virtual/TimelineStartRoomTimelineItem.swift index 7da47d39c..d38e1addf 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Virtual/TimelineStartRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Virtual/TimelineStartRoomTimelineItem.swift @@ -8,6 +8,7 @@ import Foundation struct TimelineStartRoomTimelineItem: DecorationTimelineItemProtocol, Equatable { + // Using a static identifier makes the animations consistent in SwiftUI let id: TimelineItemIdentifier = .virtual(uniqueID: .init("TimelineStart")) let name: String? }