Permalink polish (#2771)

* Fix unbalanced padding.

* Animate the scroll to an event when it is already loaded.

* Perform the timeline clearance immediately before replacing the items.
Hides the empty timeline that was briefly shown.

* Make switching timeline more resilient.
This commit is contained in:
Doug
2024-05-02 17:51:38 +01:00
committed by GitHub
parent ec19b5a0c4
commit d067e844da
23 changed files with 152 additions and 98 deletions
@@ -20,7 +20,8 @@ enum RoomTimelineItemFixtures {
/// The default timeline items used in Xcode previews etc.
static var `default`: [RoomTimelineItemProtocol] = [
SeparatorRoomTimelineItem(id: .init(timelineID: "Yesterday"), text: "Yesterday"),
TextRoomTimelineItem(id: .random,
TextRoomTimelineItem(id: .init(timelineID: ".RoomTimelineItemFixtures.default.0",
eventID: "RoomTimelineItemFixtures.default.0"),
timestamp: "10:10 AM",
isOutgoing: false,
isEditable: false,
@@ -29,7 +30,8 @@ enum RoomTimelineItemFixtures {
sender: .init(id: "", displayName: "Jacob"),
content: .init(body: "That looks so good!"),
properties: RoomTimelineItemProperties(isEdited: true)),
TextRoomTimelineItem(id: .random,
TextRoomTimelineItem(id: .init(timelineID: "RoomTimelineItemFixtures.default.1",
eventID: "RoomTimelineItemFixtures.default.1"),
timestamp: "10:11 AM",
isOutgoing: false,
isEditable: false,
@@ -40,7 +42,8 @@ enum RoomTimelineItemFixtures {
properties: RoomTimelineItemProperties(reactions: [
AggregatedReaction(accountOwnerID: "me", key: "🙌", senders: [ReactionSender(id: "me", timestamp: Date())])
])),
TextRoomTimelineItem(id: .random,
TextRoomTimelineItem(id: .init(timelineID: "RoomTimelineItemFixtures.default.2",
eventID: "RoomTimelineItemFixtures.default.2"),
timestamp: "10:11 AM",
isOutgoing: false,
isEditable: false,
@@ -59,7 +62,8 @@ enum RoomTimelineItemFixtures {
])
])),
SeparatorRoomTimelineItem(id: .init(timelineID: "Today"), text: "Today"),
TextRoomTimelineItem(id: .random,
TextRoomTimelineItem(id: .init(timelineID: "RoomTimelineItemFixtures.default.3",
eventID: "RoomTimelineItemFixtures.default.3"),
timestamp: "5 PM",
isOutgoing: false,
isEditable: false,
@@ -68,7 +72,8 @@ enum RoomTimelineItemFixtures {
sender: .init(id: "", displayName: "Helena"),
content: .init(body: "Wow, cool. Ok, lets go the usual place tomorrow?! Is that too soon? Here’s the menu, let me know what you want it’s on me!"),
properties: RoomTimelineItemProperties(orderedReadReceipts: [ReadReceipt(userID: "alice", formattedTimestamp: nil)])),
TextRoomTimelineItem(id: .random,
TextRoomTimelineItem(id: .init(timelineID: "RoomTimelineItemFixtures.default.4",
eventID: "RoomTimelineItemFixtures.default.4"),
timestamp: "5 PM",
isOutgoing: true,
isEditable: true,
@@ -76,7 +81,8 @@ enum RoomTimelineItemFixtures {
isThreaded: false,
sender: .init(id: "", displayName: "Bob"),
content: .init(body: "And John's speech was amazing!")),
TextRoomTimelineItem(id: .random,
TextRoomTimelineItem(id: .init(timelineID: "RoomTimelineItemFixtures.default.5",
eventID: "RoomTimelineItemFixtures.default.5"),
timestamp: "5 PM",
isOutgoing: true,
isEditable: true,
@@ -89,7 +95,8 @@ enum RoomTimelineItemFixtures {
ReadReceipt(userID: "bob", formattedTimestamp: nil),
ReadReceipt(userID: "charlie", formattedTimestamp: nil),
ReadReceipt(userID: "dan", formattedTimestamp: nil)])),
TextRoomTimelineItem(id: .random,
TextRoomTimelineItem(id: .init(timelineID: "RoomTimelineItemFixtures.default.6",
eventID: "RoomTimelineItemFixtures.default.6"),
timestamp: "5 PM",
isOutgoing: false,
isEditable: false,
@@ -165,7 +165,7 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
let incomingItem = incomingItems.removeFirst()
timelineItems.append(incomingItem)
callbacks.send(.updatedTimelineItems)
callbacks.send(.updatedTimelineItems(timelineItems: timelineItems, isSwitchingTimelines: false))
try client?.send(.success)
}
@@ -181,7 +181,7 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
let newItems = backPaginationResponses.removeFirst()
timelineItems.insert(contentsOf: newItems, at: 0)
callbacks.send(.updatedTimelineItems)
callbacks.send(.updatedTimelineItems(timelineItems: timelineItems, isSwitchingTimelines: false))
try client?.send(.success)
}
@@ -33,7 +33,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
private var activeTimeline: TimelineProxyProtocol
private var activeTimelineProvider: RoomTimelineProviderProtocol {
didSet {
configureActiveTimelineProvider(clearExistingItems: true)
configureActiveTimelineProvider()
}
}
@@ -272,31 +272,26 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
/// The cancellable used to update the timeline items.
private var updateTimelineItemsCancellable: AnyCancellable?
/// The controller is switching the `activeTimelineProvider`.
private var isSwitchingTimelines = false
/// Configures the controller to listen to `activeTimeline` for events.
/// - Parameter clearExistingItems: Whether or not to clear any existing items before loading the timeline's contents.
private func configureActiveTimelineProvider(clearExistingItems: Bool = false) {
private func configureActiveTimelineProvider() {
updateTimelineItemsCancellable = nil
isSwitchingTimelines = true
// Inform the world that the initial items are loading from the store
callbacks.send(.paginationState(.init(backward: .paginating, forward: .paginating)))
callbacks.send(.isLive(activeTimelineProvider.isLive))
updateTimelineItemsCancellable = activeTimelineProvider
.updatePublisher
.receive(on: serialDispatchQueue)
.sink { [weak self] items, paginationState in
self?.updateTimelineItems(itemProxies: items, paginationState: paginationState)
}
// Inform the world that the initial items are loading from the store
callbacks.send(.paginationState(.init(backward: .paginating, forward: .paginating)))
callbacks.send(.isLive(activeTimelineProvider.isLive))
if clearExistingItems {
// Transition through empty to prevent animations.
timelineItems.removeAll()
callbacks.send(.updatedTimelineItems)
}
serialDispatchQueue.async { [activeTimelineProvider] in
self.updateTimelineItems(itemProxies: activeTimelineProvider.itemProxies, paginationState: activeTimelineProvider.paginationState)
self.callbacks.send(.paginationState(.init(backward: .idle, forward: .idle)))
}
}
@objc private func contentSizeCategoryDidChange() {
@@ -309,6 +304,9 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
private func updateTimelineItems(itemProxies: [TimelineItemProxy], paginationState: PaginationState) {
var newTimelineItems = [RoomTimelineItemProtocol]()
let isNewTimeline = isSwitchingTimelines
isSwitchingTimelines = false
let collapsibleChunks = itemProxies.groupBy { isItemCollapsible($0) }
for (index, collapsibleChunk) in collapsibleChunks.enumerated() {
@@ -362,7 +360,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
timelineItems = newTimelineItems
}
callbacks.send(.updatedTimelineItems)
callbacks.send(.updatedTimelineItems(timelineItems: newTimelineItems, isSwitchingTimelines: isNewTimeline))
callbacks.send(.paginationState(paginationState))
}
@@ -19,7 +19,7 @@ import Foundation
import UIKit
enum RoomTimelineControllerCallback {
case updatedTimelineItems
case updatedTimelineItems(timelineItems: [RoomTimelineItemProtocol], isSwitchingTimelines: Bool)
case paginationState(PaginationState)
case isLive(Bool)
}