mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Rename the TimelineProvider to TimelineItemProvider
This commit is contained in:
committed by
Stefan Ceriu
parent
d058e7cd25
commit
972084aaf7
@@ -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)
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user