Rename the TimelineProvider to TimelineItemProvider

This commit is contained in:
Stefan Ceriu
2025-05-23 07:21:25 +03:00
committed by Stefan Ceriu
parent d058e7cd25
commit 972084aaf7
18 changed files with 102 additions and 119 deletions
@@ -15297,7 +15297,7 @@ class TimelineControllerFactoryMock: TimelineControllerFactoryProtocol, @uncheck
}
}
}
class TimelineProviderMock: TimelineProviderProtocol, @unchecked Sendable {
class TimelineItemProviderMock: TimelineItemProviderProtocol, @unchecked Sendable {
var updatePublisher: AnyPublisher<([TimelineItemProxy], PaginationState), Never> {
get { return underlyingUpdatePublisher }
set(value) { underlyingUpdatePublisher = value }
@@ -15322,11 +15322,11 @@ class TimelineProviderMock: TimelineProviderProtocol, @unchecked Sendable {
}
class TimelineProxyMock: TimelineProxyProtocol, @unchecked Sendable {
var timelineProvider: TimelineProviderProtocol {
get { return underlyingTimelineProvider }
set(value) { underlyingTimelineProvider = value }
var timelineItemProvider: TimelineItemProviderProtocol {
get { return underlyingTimelineItemProvider }
set(value) { underlyingTimelineItemProvider = value }
}
var underlyingTimelineProvider: TimelineProviderProtocol!
var underlyingTimelineItemProvider: TimelineItemProviderProtocol!
//MARK: - subscribeForUpdates
@@ -10,7 +10,7 @@ import Foundation
import MatrixRustSDK
@MainActor
class AutoUpdatingTimelineProviderMock: TimelineProvider {
class AutoUpdatingTimelineItemProviderMock: TimelineItemProvider {
static var timelineListener: TimelineListener?
private let innerPaginationStatePublisher: PassthroughSubject<PaginationState, Never>
@@ -24,12 +24,12 @@ extension TimelineProxyMock {
sendReadReceiptForTypeReturnValue = .success(())
if configuration.isAutoUpdating {
underlyingTimelineProvider = AutoUpdatingTimelineProviderMock()
underlyingTimelineItemProvider = AutoUpdatingTimelineItemProviderMock()
} else {
let timelineProvider = TimelineProviderMock()
timelineProvider.paginationState = .init(backward: configuration.timelineStartReached ? .timelineEndReached : .idle, forward: .timelineEndReached)
timelineProvider.underlyingMembershipChangePublisher = PassthroughSubject().eraseToAnyPublisher()
underlyingTimelineProvider = timelineProvider
let timelineItemProvider = TimelineItemProviderMock()
timelineItemProvider.paginationState = .init(backward: configuration.timelineStartReached ? .timelineEndReached : .idle, forward: .timelineEndReached)
timelineItemProvider.underlyingMembershipChangePublisher = PassthroughSubject().eraseToAnyPublisher()
underlyingTimelineItemProvider = timelineItemProvider
}
}
}
@@ -19,7 +19,7 @@ class NetworkMonitor: NetworkMonitorProtocol {
}
init() {
queue = DispatchQueue(label: "io.element.elementx.networkmonitor", qos: .background)
queue = DispatchQueue(label: "io.element.elementx.network_monitor", qos: .background)
pathMonitor = NWPathMonitor()
reachabilitySubject = CurrentValueSubject<NetworkMonitorReachability, Never>(.reachable)
+1 -1
View File
@@ -94,7 +94,7 @@ extension SDKListener: ProgressWatcher where T == Double {
}
}
// MARK: TimelineProvider
// MARK: TimelineItemProvider
extension SDKListener: TimelineListener where T == [TimelineDiff] {
func onUpdate(diff: [TimelineDiff]) { onUpdateClosure(diff) }
@@ -45,7 +45,7 @@ class RoomChangeRolesScreenViewModel: RoomChangeRolesScreenViewModelType, RoomCh
}
.store(in: &cancellables)
roomProxy.timeline.timelineProvider.membershipChangePublisher.sink { [roomProxy] in
roomProxy.timeline.timelineItemProvider.membershipChangePublisher.sink { [roomProxy] in
Task { await roomProxy.updateMembers() }
}
.store(in: &cancellables)
@@ -20,15 +20,15 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
private let attributedStringBuilder: AttributedStringBuilderProtocol
private let appSettings: AppSettings
private var pinnedEventsTimelineProvider: TimelineProviderProtocol? {
private var pinnedEventsTimelineItemProvider: TimelineItemProviderProtocol? {
didSet {
guard let pinnedEventsTimelineProvider else {
guard let pinnedEventsTimelineItemProvider else {
return
}
state.pinnedEventsActionState = .loaded(numberOfItems: pinnedEventsTimelineProvider.itemProxies.filter(\.isEvent).count)
state.pinnedEventsActionState = .loaded(numberOfItems: pinnedEventsTimelineItemProvider.itemProxies.filter(\.isEvent).count)
pinnedEventsTimelineProvider.updatePublisher
pinnedEventsTimelineItemProvider.updatePublisher
// When pinning or unpinning an item, the timeline might return empty for a short while, so we need to debounce it to prevent weird UI behaviours like the banner disappearing
.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main)
.sink { [weak self] updatedItems, _ in
@@ -86,7 +86,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
.filter { $0 == .reachable }
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
self?.setupPinnedEventsTimelineProviderIfNeeded()
self?.setupPinnedEventsTimelineItemProviderIfNeeded()
}
.store(in: &cancellables)
@@ -439,8 +439,8 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
}
}
private func setupPinnedEventsTimelineProviderIfNeeded() {
guard pinnedEventsTimelineProvider == nil else {
private func setupPinnedEventsTimelineItemProviderIfNeeded() {
guard pinnedEventsTimelineItemProvider == nil else {
return
}
@@ -449,8 +449,8 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
return
}
if pinnedEventsTimelineProvider == nil {
pinnedEventsTimelineProvider = pinnedEventsTimeline.timelineProvider
if pinnedEventsTimelineItemProvider == nil {
pinnedEventsTimelineItemProvider = pinnedEventsTimeline.timelineItemProvider
}
}
}
@@ -78,7 +78,7 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
}
.store(in: &cancellables)
roomProxy.timeline.timelineProvider.membershipChangePublisher.sink { [roomProxy] _ in
roomProxy.timeline.timelineItemProvider.membershipChangePublisher.sink { [roomProxy] _ in
Task { await roomProxy.updateMembers() }
}
.store(in: &cancellables)
@@ -32,14 +32,14 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
actionsSubject.eraseToAnyPublisher()
}
private var pinnedEventsTimelineProvider: TimelineProviderProtocol? {
private var pinnedEventsTimelineItemProvider: TimelineItemProviderProtocol? {
didSet {
guard let pinnedEventsTimelineProvider else {
guard let pinnedEventsTimelineItemProvider else {
return
}
buildPinnedEventContents(timelineItems: pinnedEventsTimelineProvider.itemProxies)
pinnedEventsTimelineProvider.updatePublisher
buildPinnedEventContents(timelineItems: pinnedEventsTimelineItemProvider.itemProxies)
pinnedEventsTimelineItemProvider.updatePublisher
// When pinning or unpinning an item, the timeline might return empty for a short while, so we need to debounce it to prevent weird UI behaviours like the banner disappearing
.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main)
.sink { [weak self] updatedItems, _ in
@@ -193,7 +193,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
.filter { $0 == .reachable }
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
self?.setupPinnedEventsTimelineProviderIfNeeded()
self?.setupPinnedEventsTimelineItemProviderIfNeeded()
}
.store(in: &cancellables)
@@ -347,8 +347,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
state.canBan = await (try? roomProxy.canUserBan(userID: ownUserID).get()) == true
}
private func setupPinnedEventsTimelineProviderIfNeeded() {
guard pinnedEventsTimelineProvider == nil else {
private func setupPinnedEventsTimelineItemProviderIfNeeded() {
guard pinnedEventsTimelineItemProvider == nil else {
return
}
@@ -357,8 +357,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
return
}
if pinnedEventsTimelineProvider == nil {
pinnedEventsTimelineProvider = pinnedEventsTimeline.timelineProvider
if pinnedEventsTimelineItemProvider == nil {
pinnedEventsTimelineItemProvider = pinnedEventsTimeline.timelineItemProvider
}
}
}
@@ -63,7 +63,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
notificationSettings: NotificationSettingsProxyProtocol,
appSettings: AppSettings) {
self.roomListService = roomListService
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomsummaryprovider", qos: .default)
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.room_summary_provider", qos: .default)
self.eventStringBuilder = eventStringBuilder
self.name = name
self.shouldUpdateVisibleRange = shouldUpdateVisibleRange
@@ -13,7 +13,7 @@ import MatrixRustSDK
final class RoomDirectorySearchProxy: RoomDirectorySearchProxyProtocol {
private let roomDirectorySearch: RoomDirectorySearchProtocol
private let appSettings: AppSettings
private let serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomdirectorysearch", qos: .default)
private let serialDispatchQueue = DispatchQueue(label: "io.element.elementx.room_directory_search_proxy", qos: .default)
private let resultsSubject = CurrentValueSubject<[RoomDirectorySearchResult], Never>([])
@@ -13,19 +13,17 @@ import UIKit
class TimelineController: TimelineControllerProtocol {
private let roomProxy: JoinedRoomProxyProtocol
private let liveTimelineProvider: TimelineProviderProtocol
private let liveTimelineItemProvider: TimelineItemProviderProtocol
private let timelineItemFactory: RoomTimelineItemFactoryProtocol
private let mediaProvider: MediaProviderProtocol
private let appSettings: AppSettings
private let serialDispatchQueue: DispatchQueue
let callbacks = PassthroughSubject<TimelineControllerCallback, Never>()
private var activeTimeline: TimelineProxyProtocol
private var activeTimelineProvider: TimelineProviderProtocol {
private var activeTimelineItemProvider: TimelineItemProviderProtocol {
didSet {
configureActiveTimelineProvider()
configureActiveTimelineItemProvider()
}
}
@@ -42,7 +40,7 @@ class TimelineController: TimelineControllerProtocol {
}
var timelineKind: TimelineKind {
activeTimelineProvider.kind
activeTimelineItemProvider.kind
}
init(roomProxy: JoinedRoomProxyProtocol,
@@ -52,18 +50,16 @@ class TimelineController: TimelineControllerProtocol {
mediaProvider: MediaProviderProtocol,
appSettings: AppSettings) {
self.roomProxy = roomProxy
liveTimelineProvider = timelineProxy.timelineProvider
liveTimelineItemProvider = timelineProxy.timelineItemProvider
self.timelineItemFactory = timelineItemFactory
self.mediaProvider = mediaProvider
self.appSettings = appSettings
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.timelineprovider", qos: .utility)
activeTimeline = timelineProxy
activeTimelineProvider = liveTimelineProvider
activeTimelineItemProvider = liveTimelineItemProvider
guard let initialFocussedEventID else {
configureActiveTimelineProvider()
configureActiveTimelineItemProvider()
return
}
@@ -75,7 +71,7 @@ class TimelineController: TimelineControllerProtocol {
break
case .failure:
// Setup the live timeline as a fallback.
configureActiveTimelineProvider()
configureActiveTimelineItemProvider()
}
}
}
@@ -85,7 +81,7 @@ class TimelineController: TimelineControllerProtocol {
case .success(let timeline):
await timeline.subscribeForUpdates()
activeTimeline = timeline
activeTimelineProvider = timeline.timelineProvider
activeTimelineItemProvider = timeline.timelineItemProvider
return .success(())
case .failure(let error):
if case .eventNotFound = error {
@@ -98,7 +94,7 @@ class TimelineController: TimelineControllerProtocol {
func focusLive() {
activeTimeline = roomProxy.timeline
activeTimelineProvider = liveTimelineProvider
activeTimelineItemProvider = liveTimelineItemProvider
}
func paginateBackwards(requestSize: UInt16) async -> Result<Void, TimelineControllerError> {
@@ -328,7 +324,7 @@ class TimelineController: TimelineControllerProtocol {
// Handle this parallel to the timeline items so we're not forced
// to bundle the Rust side objects within them
func debugInfo(for itemID: TimelineItemIdentifier) -> TimelineItemDebugInfo {
for timelineItemProxy in activeTimelineProvider.itemProxies {
for timelineItemProxy in activeTimelineItemProvider.itemProxies {
switch timelineItemProxy {
case .event(let item):
if item.id == itemID {
@@ -343,7 +339,7 @@ class TimelineController: TimelineControllerProtocol {
}
func sendHandle(for itemID: TimelineItemIdentifier) -> SendHandleProxy? {
for timelineItemProxy in activeTimelineProvider.itemProxies {
for timelineItemProxy in activeTimelineItemProvider.itemProxies {
switch timelineItemProxy {
case .event(let item):
if item.id == itemID {
@@ -361,24 +357,24 @@ class TimelineController: TimelineControllerProtocol {
/// The cancellable used to update the timeline items.
private var updateTimelineItemsCancellable: AnyCancellable?
/// The controller is switching the `activeTimelineProvider`.
/// The controller is switching the `activeTimelineItemProvider`.
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() {
private func configureActiveTimelineItemProvider() {
updateTimelineItemsCancellable = nil
isSwitchingTimelines = true
// Inform the world that the initial items are loading from the store
paginationState = PaginationState(backward: .paginating, forward: .paginating)
callbacks.send(.isLive(activeTimelineProvider.kind == .live))
callbacks.send(.isLive(activeTimelineItemProvider.kind == .live))
updateTimelineItemsCancellable = Task { [weak self, activeTimelineProvider] in
updateTimelineItemsCancellable = Task { [weak self, activeTimelineItemProvider] in
let contentSizeChangePublisher = NotificationCenter.default.publisher(for: UIContentSizeCategory.didChangeNotification)
let timelineUpdates = activeTimelineProvider.updatePublisher.merge(with: contentSizeChangePublisher.map { _ in
(activeTimelineProvider.itemProxies, activeTimelineProvider.paginationState)
let timelineUpdates = activeTimelineItemProvider.updatePublisher.merge(with: contentSizeChangePublisher.map { _ in
(activeTimelineItemProvider.itemProxies, activeTimelineItemProvider.paginationState)
})
for await (items, paginationState) in timelineUpdates.values {
@@ -506,7 +502,7 @@ class TimelineController: TimelineControllerProtocol {
}
func eventTimestamp(for itemID: TimelineItemIdentifier) -> Date? {
for itemProxy in activeTimelineProvider.itemProxies {
for itemProxy in activeTimelineItemProvider.itemProxies {
switch itemProxy {
case .event(let eventTimelineItemProxy):
if eventTimelineItemProxy.id == itemID {
@@ -9,7 +9,7 @@ import Combine
import Foundation
import MatrixRustSDK
class TimelineProvider: TimelineProviderProtocol {
class TimelineItemProvider: TimelineItemProviderProtocol {
private var cancellables = Set<AnyCancellable>()
private let serialDispatchQueue: DispatchQueue
@@ -46,7 +46,7 @@ class TimelineProvider: TimelineProviderProtocol {
}
init(timeline: Timeline, kind: TimelineKind, paginationStatePublisher: AnyPublisher<PaginationState, Never>) {
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.timelineprovider", qos: .utility)
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.timeline_item_provider", qos: .utility)
itemProxiesSubject = CurrentValueSubject<[TimelineItemProxy], Never>([])
self.kind = kind
@@ -29,7 +29,7 @@ struct PaginationState: Equatable {
@MainActor
// sourcery: AutoMockable
protocol TimelineProviderProtocol {
protocol TimelineItemProviderProtocol {
/// A publisher that signals when ``itemProxies`` or ``paginationState`` are changed.
var updatePublisher: AnyPublisher<([TimelineItemProxy], PaginationState), Never> { get }
/// The current set of items in the timeline.
@@ -24,9 +24,9 @@ final class TimelineProxy: TimelineProxyProtocol {
private let kind: TimelineKind
private var innerTimelineProvider: TimelineProviderProtocol!
var timelineProvider: TimelineProviderProtocol {
innerTimelineProvider
private var innerTimelineItemProvider: TimelineItemProviderProtocol!
var timelineItemProvider: TimelineItemProviderProtocol {
innerTimelineItemProvider
}
deinit {
@@ -39,7 +39,7 @@ final class TimelineProxy: TimelineProxyProtocol {
}
func subscribeForUpdates() async {
guard innerTimelineProvider == nil else {
guard innerTimelineItemProvider == nil else {
MXLog.warning("Timeline already subscribed for updates")
return
}
@@ -51,12 +51,12 @@ final class TimelineProxy: TimelineProxyProtocol {
await subscribeToPagination()
let provider = await TimelineProvider(timeline: timeline, kind: kind, paginationStatePublisher: paginationStatePublisher)
let provider = await TimelineItemProvider(timeline: timeline, kind: kind, paginationStatePublisher: paginationStatePublisher)
// Make sure the existing items are built so that we have content in the timeline before
// determining whether or not the timeline should paginate to load more items.
await provider.waitForInitialItems()
innerTimelineProvider = provider
innerTimelineItemProvider = provider
Task {
await timeline.fetchMembers()
@@ -76,7 +76,7 @@ final class TimelineProxy: TimelineProxyProtocol {
}
func messageEventContent(for timelineItemID: TimelineItemIdentifier) async -> RoomMessageEventContentWithoutRelation? {
guard let content = await timelineProvider.itemProxies.firstEventTimelineItemUsingStableID(timelineItemID)?.content,
guard let content = await timelineItemProvider.itemProxies.firstEventTimelineItemUsingStableID(timelineItemID)?.content,
case let .msgLike(messageLikeContent) = content,
case let .message(messageContent) = messageLikeContent.kind else {
return nil
@@ -39,7 +39,7 @@ enum TimelineProxyError: Error {
// sourcery: AutoMockable
protocol TimelineProxyProtocol {
var timelineProvider: TimelineProviderProtocol { get }
var timelineItemProvider: TimelineItemProviderProtocol { get }
func subscribeForUpdates() async