diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/CollapsibleRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/CollapsibleRoomTimelineView.swift index d82c8dca6..caa073908 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/CollapsibleRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/CollapsibleRoomTimelineView.swift @@ -44,8 +44,8 @@ struct CollapsibleRoomTimelineView: View { struct CollapsibleRoomTimelineView_Previews: PreviewProvider { static var previews: some View { - let item = CollapsibleTimelineItem(items: [SeparatorRoomTimelineItem(text: "This is a separator"), - SeparatorRoomTimelineItem(text: "This is another separator")]) + let item = CollapsibleTimelineItem(items: [SeparatorRoomTimelineItem(id: "First separator", text: "This is a separator"), + SeparatorRoomTimelineItem(id: "Second separator", text: "This is another separator")]) CollapsibleRoomTimelineView(timelineItem: item) } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/ReadMarkerRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/ReadMarkerRoomTimelineView.swift index 1f529377d..fabb1157a 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/ReadMarkerRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/ReadMarkerRoomTimelineView.swift @@ -41,7 +41,7 @@ struct ReadMarkerRoomTimelineView_Previews: PreviewProvider { static let item = ReadMarkerRoomTimelineItem() static var previews: some View { VStack(alignment: .leading, spacing: 0) { - RoomTimelineViewProvider.separator(.init(text: "Today"), .single) + RoomTimelineViewProvider.separator(.init(id: "Separator", text: "Today"), .single) RoomTimelineViewProvider.text(.init(id: "", timestamp: "", isOutgoing: true, @@ -51,7 +51,7 @@ struct ReadMarkerRoomTimelineView_Previews: PreviewProvider { ReadMarkerRoomTimelineView(timelineItem: item) - RoomTimelineViewProvider.separator(.init(text: "Today"), .single) + RoomTimelineViewProvider.separator(.init(id: "Separator", text: "Today"), .single) RoomTimelineViewProvider.text(.init(id: "", timestamp: "", isOutgoing: false, diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/SeparatorRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/SeparatorRoomTimelineView.swift index f2e99108c..af613a866 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/SeparatorRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/SeparatorRoomTimelineView.swift @@ -31,7 +31,7 @@ struct SeparatorRoomTimelineView: View { struct SeparatorRoomTimelineView_Previews: PreviewProvider { static var previews: some View { - let item = SeparatorRoomTimelineItem(text: "This is a separator") + let item = SeparatorRoomTimelineItem(id: "Separator", text: "This is a separator") SeparatorRoomTimelineView(timelineItem: item) } } diff --git a/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift b/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift index d7ab1b80e..b6bf5ee9f 100644 --- a/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift +++ b/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift @@ -19,7 +19,7 @@ import Foundation enum RoomTimelineItemFixtures { /// The default timeline items used in Xcode previews etc. static var `default`: [RoomTimelineItemProtocol] = [ - SeparatorRoomTimelineItem(text: "Yesterday"), + SeparatorRoomTimelineItem(id: "Yesterday", text: "Yesterday"), TextRoomTimelineItem(id: UUID().uuidString, timestamp: "10:10 AM", isOutgoing: false, @@ -46,7 +46,7 @@ enum RoomTimelineItemFixtures { AggregatedReaction(key: "🙏", count: 1, isHighlighted: false), AggregatedReaction(key: "🙌", count: 2, isHighlighted: true) ])), - SeparatorRoomTimelineItem(text: "Today"), + SeparatorRoomTimelineItem(id: "Today", text: "Today"), TextRoomTimelineItem(id: UUID().uuidString, timestamp: "5 PM", isOutgoing: false, diff --git a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift index fb3230ed2..7903ef85b 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift @@ -231,8 +231,13 @@ class RoomTimelineController: RoomTimelineControllerProtocol { for (index, collapsibleChunk) in collapsibleChunks.enumerated() { let isLastItem = index == collapsibleChunks.indices.last + // Try building a stable identifier for items that don't have one + // We need to avoid duplicates otherwise the diffable datasource will crash + let reversedIndex = collapsibleChunks.count - index + let items = collapsibleChunk.compactMap { itemProxy in - let timelineItem = buildTimelineItem(for: itemProxy) + + let timelineItem = buildTimelineItem(for: itemProxy, chunkIndex: reversedIndex) if timelineItem is PaginationIndicatorRoomTimelineItem { isBackPaginating = true @@ -267,17 +272,19 @@ class RoomTimelineController: RoomTimelineControllerProtocol { callbacks.send(.isBackPaginating(isBackPaginating)) } - private func buildTimelineItem(for itemProxy: TimelineItemProxy) -> RoomTimelineItemProtocol? { + private func buildTimelineItem(for itemProxy: TimelineItemProxy, chunkIndex: Int) -> RoomTimelineItemProtocol? { switch itemProxy { case .event(let eventTimelineItem): return timelineItemFactory.buildTimelineItem(for: eventTimelineItem) case .virtual(let virtualItem): switch virtualItem { case .dayDivider(let timestamp): - // These components will be replaced by a timestamp in upcoming releases let date = Date(timeIntervalSince1970: TimeInterval(timestamp / 1000)) let dateString = date.formatted(date: .complete, time: .omitted) - return SeparatorRoomTimelineItem(text: dateString) + + // Separators without stable identifiers cause UI glitches + let identifier = "\(chunkIndex)-\(dateString)" + return SeparatorRoomTimelineItem(id: identifier, text: dateString) case .readMarker: return ReadMarkerRoomTimelineItem() case .loadingIndicator: diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Virtual/SeparatorRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Virtual/SeparatorRoomTimelineItem.swift index 23104c360..81458ee29 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Virtual/SeparatorRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Virtual/SeparatorRoomTimelineItem.swift @@ -17,7 +17,6 @@ import Foundation struct SeparatorRoomTimelineItem: DecorationTimelineItemProtocol, Identifiable, Hashable { - #warning("This should really be a stable identifier, ideally coming from the SDK, but unfortunately we receive duplicates for it at the moment, crashing our timeline data source") - let id: String = UUID().uuidString + let id: String let text: String }