RoomTimeline… refactor (drop the Room). (#3728)

* Add the timeline controller factory to the timeline view model.

In preparation for building a timeline to swipe through media in QuickLook.

* Refactor RoomTimelineControllerFactory.

* Refactor RoomTimelineController.

* Refactor RoomTimelineProvider.
This commit is contained in:
Doug
2025-02-03 14:14:01 +00:00
committed by GitHub
parent 9df5a8fddb
commit d195e603c9
53 changed files with 688 additions and 631 deletions
@@ -534,7 +534,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
appLockService: appLockFlowCoordinator.appLockService,
bugReportService: ServiceLocator.shared.bugReportService,
elementCallService: elementCallService,
roomTimelineControllerFactory: RoomTimelineControllerFactory(),
timelineControllerFactory: TimelineControllerFactory(),
appMediator: appMediator,
appSettings: appSettings,
appHooks: appHooks,
@@ -16,7 +16,7 @@ enum MediaEventsTimelineFlowCoordinatorAction {
class MediaEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
private let navigationStackCoordinator: NavigationStackCoordinator
private let userSession: UserSessionProtocol
private let roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol
private let timelineControllerFactory: TimelineControllerFactoryProtocol
private let roomProxy: JoinedRoomProxyProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private let appMediator: AppMediatorProtocol
@@ -31,14 +31,14 @@ class MediaEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
init(navigationStackCoordinator: NavigationStackCoordinator,
userSession: UserSessionProtocol,
roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol,
timelineControllerFactory: TimelineControllerFactoryProtocol,
roomProxy: JoinedRoomProxyProtocol,
userIndicatorController: UserIndicatorControllerProtocol,
appMediator: AppMediatorProtocol,
emojiProvider: EmojiProviderProtocol) {
self.navigationStackCoordinator = navigationStackCoordinator
self.userSession = userSession
self.roomTimelineControllerFactory = roomTimelineControllerFactory
self.timelineControllerFactory = timelineControllerFactory
self.roomProxy = roomProxy
self.userIndicatorController = userIndicatorController
self.appMediator = appMediator
@@ -64,18 +64,20 @@ class MediaEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
stateEventStringBuilder: RoomStateEventStringBuilder(userID: userSession.clientProxy.userID))
guard case let .success(mediaTimelineController) = await roomTimelineControllerFactory.buildMessageFilteredRoomTimelineController(allowedMessageTypes: [.image, .video],
roomProxy: roomProxy,
timelineItemFactory: timelineItemFactory,
mediaProvider: userSession.mediaProvider) else {
guard case let .success(mediaTimelineController) = await timelineControllerFactory.buildMessageFilteredTimelineController(allowedMessageTypes: [.image, .video],
presentation: .mediaFilesScreen,
roomProxy: roomProxy,
timelineItemFactory: timelineItemFactory,
mediaProvider: userSession.mediaProvider) else {
MXLog.error("Failed presenting media timeline")
return
}
guard case let .success(filesTimelineController) = await roomTimelineControllerFactory.buildMessageFilteredRoomTimelineController(allowedMessageTypes: [.file, .audio],
roomProxy: roomProxy,
timelineItemFactory: timelineItemFactory,
mediaProvider: userSession.mediaProvider) else {
guard case let .success(filesTimelineController) = await timelineControllerFactory.buildMessageFilteredTimelineController(allowedMessageTypes: [.file, .audio],
presentation: .mediaFilesScreen,
roomProxy: roomProxy,
timelineItemFactory: timelineItemFactory,
mediaProvider: userSession.mediaProvider) else {
MXLog.error("Failed presenting media timeline")
return
}
@@ -88,7 +90,8 @@ class MediaEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
voiceMessageMediaManager: userSession.voiceMessageMediaManager,
appMediator: appMediator,
emojiProvider: emojiProvider,
userIndicatorController: userIndicatorController)
userIndicatorController: userIndicatorController,
timelineControllerFactory: timelineControllerFactory)
let coordinator = MediaEventsTimelineScreenCoordinator(parameters: parameters)
@@ -18,7 +18,7 @@ enum PinnedEventsTimelineFlowCoordinatorAction {
class PinnedEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
private let navigationStackCoordinator: NavigationStackCoordinator
private let userSession: UserSessionProtocol
private let roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol
private let timelineControllerFactory: TimelineControllerFactoryProtocol
private let roomProxy: JoinedRoomProxyProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private let appMediator: AppMediatorProtocol
@@ -33,14 +33,14 @@ class PinnedEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
init(navigationStackCoordinator: NavigationStackCoordinator,
userSession: UserSessionProtocol,
roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol,
timelineControllerFactory: TimelineControllerFactoryProtocol,
roomProxy: JoinedRoomProxyProtocol,
userIndicatorController: UserIndicatorControllerProtocol,
appMediator: AppMediatorProtocol,
emojiProvider: EmojiProviderProtocol) {
self.navigationStackCoordinator = navigationStackCoordinator
self.userSession = userSession
self.roomTimelineControllerFactory = roomTimelineControllerFactory
self.timelineControllerFactory = timelineControllerFactory
self.roomProxy = roomProxy
self.userIndicatorController = userIndicatorController
self.appMediator = appMediator
@@ -65,9 +65,9 @@ class PinnedEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID))
guard let timelineController = await roomTimelineControllerFactory.buildPinnedEventsRoomTimelineController(roomProxy: roomProxy,
timelineItemFactory: timelineItemFactory,
mediaProvider: userSession.mediaProvider) else {
guard let timelineController = await timelineControllerFactory.buildPinnedEventsTimelineController(roomProxy: roomProxy,
timelineItemFactory: timelineItemFactory,
mediaProvider: userSession.mediaProvider) else {
fatalError("This can never fail because we allow this view to be presented only when the timeline is fully loaded and not nil")
}
@@ -77,7 +77,8 @@ class PinnedEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
mediaPlayerProvider: MediaPlayerProvider(),
voiceMessageMediaManager: userSession.voiceMessageMediaManager,
appMediator: appMediator,
emojiProvider: emojiProvider))
emojiProvider: emojiProvider,
timelineControllerFactory: timelineControllerFactory))
coordinator.actions
.sink { [weak self] action in
@@ -78,7 +78,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
private let roomID: String
private let userSession: UserSessionProtocol
private let isChildFlow: Bool
private let roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol
private let timelineControllerFactory: TimelineControllerFactoryProtocol
private let navigationStackCoordinator: NavigationStackCoordinator
private let emojiProvider: EmojiProviderProtocol
private let ongoingCallRoomIDPublisher: CurrentValuePublisher<String?, Never>
@@ -110,12 +110,12 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
actionsSubject.eraseToAnyPublisher()
}
private var timelineController: RoomTimelineControllerProtocol?
private var timelineController: TimelineControllerProtocol?
init(roomID: String,
userSession: UserSessionProtocol,
isChildFlow: Bool,
roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol,
timelineControllerFactory: TimelineControllerFactoryProtocol,
navigationStackCoordinator: NavigationStackCoordinator,
emojiProvider: EmojiProviderProtocol,
ongoingCallRoomIDPublisher: CurrentValuePublisher<String?, Never>,
@@ -126,7 +126,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
self.roomID = roomID
self.userSession = userSession
self.isChildFlow = isChildFlow
self.roomTimelineControllerFactory = roomTimelineControllerFactory
self.timelineControllerFactory = timelineControllerFactory
self.navigationStackCoordinator = navigationStackCoordinator
self.emojiProvider = emojiProvider
self.ongoingCallRoomIDPublisher = ongoingCallRoomIDPublisher
@@ -685,10 +685,10 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
let timelineItemFactory = RoomTimelineItemFactory(userID: userID,
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID))
let timelineController = roomTimelineControllerFactory.buildRoomTimelineController(roomProxy: roomProxy,
initialFocussedEventID: presentationAction?.focusedEvent?.eventID,
timelineItemFactory: timelineItemFactory,
mediaProvider: userSession.mediaProvider)
let timelineController = timelineControllerFactory.buildTimelineController(roomProxy: roomProxy,
initialFocussedEventID: presentationAction?.focusedEvent?.eventID,
timelineItemFactory: timelineItemFactory,
mediaProvider: userSession.mediaProvider)
self.timelineController = timelineController
let completionSuggestionService = CompletionSuggestionService(roomProxy: roomProxy)
@@ -707,7 +707,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
ongoingCallRoomIDPublisher: ongoingCallRoomIDPublisher,
appMediator: appMediator,
appSettings: appSettings,
composerDraftService: composerDraftService)
composerDraftService: composerDraftService,
timelineControllerFactory: timelineControllerFactory)
let coordinator = RoomScreenCoordinator(parameters: parameters)
coordinator.actions
@@ -1202,13 +1203,13 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID))
let roomTimelineController = roomTimelineControllerFactory.buildRoomTimelineController(roomProxy: roomProxy,
initialFocussedEventID: nil,
timelineItemFactory: timelineItemFactory,
mediaProvider: userSession.mediaProvider)
let timelineController = timelineControllerFactory.buildTimelineController(roomProxy: roomProxy,
initialFocussedEventID: nil,
timelineItemFactory: timelineItemFactory,
mediaProvider: userSession.mediaProvider)
let parameters = RoomPollsHistoryScreenCoordinatorParameters(pollInteractionHandler: PollInteractionHandler(analyticsService: analytics, roomProxy: roomProxy),
roomTimelineController: roomTimelineController)
timelineController: timelineController)
let coordinator = RoomPollsHistoryScreenCoordinator(parameters: parameters)
coordinator.actions
.sink { [weak self] action in
@@ -1513,7 +1514,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
let coordinator = await RoomFlowCoordinator(roomID: roomID,
userSession: userSession,
isChildFlow: true,
roomTimelineControllerFactory: roomTimelineControllerFactory,
timelineControllerFactory: timelineControllerFactory,
navigationStackCoordinator: navigationStackCoordinator,
emojiProvider: emojiProvider,
ongoingCallRoomIDPublisher: ongoingCallRoomIDPublisher,
@@ -1551,7 +1552,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
let flowCoordinator = PinnedEventsTimelineFlowCoordinator(navigationStackCoordinator: stackCoordinator,
userSession: userSession,
roomTimelineControllerFactory: roomTimelineControllerFactory,
timelineControllerFactory: timelineControllerFactory,
roomProxy: roomProxy,
userIndicatorController: userIndicatorController,
appMediator: appMediator,
@@ -1590,7 +1591,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
private func startMediaEventsTimelineFlow() async {
let flowCoordinator = MediaEventsTimelineFlowCoordinator(navigationStackCoordinator: navigationStackCoordinator,
userSession: userSession,
roomTimelineControllerFactory: roomTimelineControllerFactory,
timelineControllerFactory: timelineControllerFactory,
roomProxy: roomProxy,
userIndicatorController: userIndicatorController,
appMediator: appMediator,
@@ -34,7 +34,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
// periphery:ignore - retaining purpose
private var roomFlowCoordinator: RoomFlowCoordinator?
private let roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol
private let timelineControllerFactory: TimelineControllerFactoryProtocol
private let settingsFlowCoordinator: SettingsFlowCoordinator
@@ -69,7 +69,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
appLockService: AppLockServiceProtocol,
bugReportService: BugReportServiceProtocol,
elementCallService: ElementCallServiceProtocol,
roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol,
timelineControllerFactory: TimelineControllerFactoryProtocol,
appMediator: AppMediatorProtocol,
appSettings: AppSettings,
appHooks: AppHooks,
@@ -81,7 +81,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
self.navigationRootCoordinator = navigationRootCoordinator
self.bugReportService = bugReportService
self.elementCallService = elementCallService
self.roomTimelineControllerFactory = roomTimelineControllerFactory
self.timelineControllerFactory = timelineControllerFactory
self.appMediator = appMediator
self.appSettings = appSettings
self.appHooks = appHooks
@@ -574,7 +574,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
let coordinator = await RoomFlowCoordinator(roomID: roomID,
userSession: userSession,
isChildFlow: false,
roomTimelineControllerFactory: roomTimelineControllerFactory,
timelineControllerFactory: timelineControllerFactory,
navigationStackCoordinator: detailNavigationStackCoordinator,
emojiProvider: EmojiProvider(appSettings: appSettings),
ongoingCallRoomIDPublisher: elementCallService.ongoingCallRoomIDPublisher,
@@ -6228,15 +6228,15 @@ class JoinedRoomProxyMock: JoinedRoomProxyProtocol, @unchecked Sendable {
}
//MARK: - messageFilteredTimeline
var messageFilteredTimelineAllowedMessageTypesUnderlyingCallsCount = 0
var messageFilteredTimelineAllowedMessageTypesCallsCount: Int {
var messageFilteredTimelineAllowedMessageTypesPresentationUnderlyingCallsCount = 0
var messageFilteredTimelineAllowedMessageTypesPresentationCallsCount: Int {
get {
if Thread.isMainThread {
return messageFilteredTimelineAllowedMessageTypesUnderlyingCallsCount
return messageFilteredTimelineAllowedMessageTypesPresentationUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = messageFilteredTimelineAllowedMessageTypesUnderlyingCallsCount
returnValue = messageFilteredTimelineAllowedMessageTypesPresentationUnderlyingCallsCount
}
return returnValue!
@@ -6244,29 +6244,29 @@ class JoinedRoomProxyMock: JoinedRoomProxyProtocol, @unchecked Sendable {
}
set {
if Thread.isMainThread {
messageFilteredTimelineAllowedMessageTypesUnderlyingCallsCount = newValue
messageFilteredTimelineAllowedMessageTypesPresentationUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
messageFilteredTimelineAllowedMessageTypesUnderlyingCallsCount = newValue
messageFilteredTimelineAllowedMessageTypesPresentationUnderlyingCallsCount = newValue
}
}
}
}
var messageFilteredTimelineAllowedMessageTypesCalled: Bool {
return messageFilteredTimelineAllowedMessageTypesCallsCount > 0
var messageFilteredTimelineAllowedMessageTypesPresentationCalled: Bool {
return messageFilteredTimelineAllowedMessageTypesPresentationCallsCount > 0
}
var messageFilteredTimelineAllowedMessageTypesReceivedAllowedMessageTypes: [RoomMessageEventMessageType]?
var messageFilteredTimelineAllowedMessageTypesReceivedInvocations: [[RoomMessageEventMessageType]] = []
var messageFilteredTimelineAllowedMessageTypesPresentationReceivedArguments: (allowedMessageTypes: [RoomMessageEventMessageType], presentation: TimelineKind.MediaPresentation)?
var messageFilteredTimelineAllowedMessageTypesPresentationReceivedInvocations: [(allowedMessageTypes: [RoomMessageEventMessageType], presentation: TimelineKind.MediaPresentation)] = []
var messageFilteredTimelineAllowedMessageTypesUnderlyingReturnValue: Result<TimelineProxyProtocol, RoomProxyError>!
var messageFilteredTimelineAllowedMessageTypesReturnValue: Result<TimelineProxyProtocol, RoomProxyError>! {
var messageFilteredTimelineAllowedMessageTypesPresentationUnderlyingReturnValue: Result<TimelineProxyProtocol, RoomProxyError>!
var messageFilteredTimelineAllowedMessageTypesPresentationReturnValue: Result<TimelineProxyProtocol, RoomProxyError>! {
get {
if Thread.isMainThread {
return messageFilteredTimelineAllowedMessageTypesUnderlyingReturnValue
return messageFilteredTimelineAllowedMessageTypesPresentationUnderlyingReturnValue
} else {
var returnValue: Result<TimelineProxyProtocol, RoomProxyError>? = nil
DispatchQueue.main.sync {
returnValue = messageFilteredTimelineAllowedMessageTypesUnderlyingReturnValue
returnValue = messageFilteredTimelineAllowedMessageTypesPresentationUnderlyingReturnValue
}
return returnValue!
@@ -6274,26 +6274,26 @@ class JoinedRoomProxyMock: JoinedRoomProxyProtocol, @unchecked Sendable {
}
set {
if Thread.isMainThread {
messageFilteredTimelineAllowedMessageTypesUnderlyingReturnValue = newValue
messageFilteredTimelineAllowedMessageTypesPresentationUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
messageFilteredTimelineAllowedMessageTypesUnderlyingReturnValue = newValue
messageFilteredTimelineAllowedMessageTypesPresentationUnderlyingReturnValue = newValue
}
}
}
}
var messageFilteredTimelineAllowedMessageTypesClosure: (([RoomMessageEventMessageType]) async -> Result<TimelineProxyProtocol, RoomProxyError>)?
var messageFilteredTimelineAllowedMessageTypesPresentationClosure: (([RoomMessageEventMessageType], TimelineKind.MediaPresentation) async -> Result<TimelineProxyProtocol, RoomProxyError>)?
func messageFilteredTimeline(allowedMessageTypes: [RoomMessageEventMessageType]) async -> Result<TimelineProxyProtocol, RoomProxyError> {
messageFilteredTimelineAllowedMessageTypesCallsCount += 1
messageFilteredTimelineAllowedMessageTypesReceivedAllowedMessageTypes = allowedMessageTypes
func messageFilteredTimeline(allowedMessageTypes: [RoomMessageEventMessageType], presentation: TimelineKind.MediaPresentation) async -> Result<TimelineProxyProtocol, RoomProxyError> {
messageFilteredTimelineAllowedMessageTypesPresentationCallsCount += 1
messageFilteredTimelineAllowedMessageTypesPresentationReceivedArguments = (allowedMessageTypes: allowedMessageTypes, presentation: presentation)
DispatchQueue.main.async {
self.messageFilteredTimelineAllowedMessageTypesReceivedInvocations.append(allowedMessageTypes)
self.messageFilteredTimelineAllowedMessageTypesPresentationReceivedInvocations.append((allowedMessageTypes: allowedMessageTypes, presentation: presentation))
}
if let messageFilteredTimelineAllowedMessageTypesClosure = messageFilteredTimelineAllowedMessageTypesClosure {
return await messageFilteredTimelineAllowedMessageTypesClosure(allowedMessageTypes)
if let messageFilteredTimelineAllowedMessageTypesPresentationClosure = messageFilteredTimelineAllowedMessageTypesPresentationClosure {
return await messageFilteredTimelineAllowedMessageTypesPresentationClosure(allowedMessageTypes, presentation)
} else {
return messageFilteredTimelineAllowedMessageTypesReturnValue
return messageFilteredTimelineAllowedMessageTypesPresentationReturnValue
}
}
//MARK: - enableEncryption
@@ -13613,243 +13613,6 @@ class RoomSummaryProviderMock: RoomSummaryProviderProtocol, @unchecked Sendable
setFilterClosure?(filter)
}
}
class RoomTimelineControllerFactoryMock: RoomTimelineControllerFactoryProtocol, @unchecked Sendable {
//MARK: - buildRoomTimelineController
var buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderUnderlyingCallsCount = 0
var buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderCallsCount: Int {
get {
if Thread.isMainThread {
return buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderUnderlyingCallsCount = newValue
}
}
}
}
var buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderCalled: Bool {
return buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderCallsCount > 0
}
var buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderReceivedArguments: (roomProxy: JoinedRoomProxyProtocol, initialFocussedEventID: String?, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol)?
var buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderReceivedInvocations: [(roomProxy: JoinedRoomProxyProtocol, initialFocussedEventID: String?, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol)] = []
var buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderUnderlyingReturnValue: RoomTimelineControllerProtocol!
var buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderReturnValue: RoomTimelineControllerProtocol! {
get {
if Thread.isMainThread {
return buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderUnderlyingReturnValue
} else {
var returnValue: RoomTimelineControllerProtocol? = nil
DispatchQueue.main.sync {
returnValue = buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderUnderlyingReturnValue = newValue
}
}
}
}
var buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderClosure: ((JoinedRoomProxyProtocol, String?, RoomTimelineItemFactoryProtocol, MediaProviderProtocol) -> RoomTimelineControllerProtocol)?
func buildRoomTimelineController(roomProxy: JoinedRoomProxyProtocol, initialFocussedEventID: String?, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol) -> RoomTimelineControllerProtocol {
buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderCallsCount += 1
buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderReceivedArguments = (roomProxy: roomProxy, initialFocussedEventID: initialFocussedEventID, timelineItemFactory: timelineItemFactory, mediaProvider: mediaProvider)
DispatchQueue.main.async {
self.buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderReceivedInvocations.append((roomProxy: roomProxy, initialFocussedEventID: initialFocussedEventID, timelineItemFactory: timelineItemFactory, mediaProvider: mediaProvider))
}
if let buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderClosure = buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderClosure {
return buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderClosure(roomProxy, initialFocussedEventID, timelineItemFactory, mediaProvider)
} else {
return buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderReturnValue
}
}
//MARK: - buildPinnedEventsRoomTimelineController
var buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount = 0
var buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderCallsCount: Int {
get {
if Thread.isMainThread {
return buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount = newValue
}
}
}
}
var buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderCalled: Bool {
return buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderCallsCount > 0
}
var buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReceivedArguments: (roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol)?
var buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReceivedInvocations: [(roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol)] = []
var buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue: RoomTimelineControllerProtocol?
var buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReturnValue: RoomTimelineControllerProtocol? {
get {
if Thread.isMainThread {
return buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue
} else {
var returnValue: RoomTimelineControllerProtocol?? = nil
DispatchQueue.main.sync {
returnValue = buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue = newValue
}
}
}
}
var buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderClosure: ((JoinedRoomProxyProtocol, RoomTimelineItemFactoryProtocol, MediaProviderProtocol) async -> RoomTimelineControllerProtocol?)?
func buildPinnedEventsRoomTimelineController(roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol) async -> RoomTimelineControllerProtocol? {
buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderCallsCount += 1
buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReceivedArguments = (roomProxy: roomProxy, timelineItemFactory: timelineItemFactory, mediaProvider: mediaProvider)
DispatchQueue.main.async {
self.buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReceivedInvocations.append((roomProxy: roomProxy, timelineItemFactory: timelineItemFactory, mediaProvider: mediaProvider))
}
if let buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderClosure = buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderClosure {
return await buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderClosure(roomProxy, timelineItemFactory, mediaProvider)
} else {
return buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReturnValue
}
}
//MARK: - buildMessageFilteredRoomTimelineController
var buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount = 0
var buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderCallsCount: Int {
get {
if Thread.isMainThread {
return buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount = newValue
}
}
}
}
var buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderCalled: Bool {
return buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderCallsCount > 0
}
var buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderReceivedArguments: (allowedMessageTypes: [RoomMessageEventMessageType], roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol)?
var buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderReceivedInvocations: [(allowedMessageTypes: [RoomMessageEventMessageType], roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol)] = []
var buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue: Result<RoomTimelineControllerProtocol, RoomTimelineFactoryControllerError>!
var buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderReturnValue: Result<RoomTimelineControllerProtocol, RoomTimelineFactoryControllerError>! {
get {
if Thread.isMainThread {
return buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue
} else {
var returnValue: Result<RoomTimelineControllerProtocol, RoomTimelineFactoryControllerError>? = nil
DispatchQueue.main.sync {
returnValue = buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue = newValue
}
}
}
}
var buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderClosure: (([RoomMessageEventMessageType], JoinedRoomProxyProtocol, RoomTimelineItemFactoryProtocol, MediaProviderProtocol) async -> Result<RoomTimelineControllerProtocol, RoomTimelineFactoryControllerError>)?
func buildMessageFilteredRoomTimelineController(allowedMessageTypes: [RoomMessageEventMessageType], roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol) async -> Result<RoomTimelineControllerProtocol, RoomTimelineFactoryControllerError> {
buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderCallsCount += 1
buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderReceivedArguments = (allowedMessageTypes: allowedMessageTypes, roomProxy: roomProxy, timelineItemFactory: timelineItemFactory, mediaProvider: mediaProvider)
DispatchQueue.main.async {
self.buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderReceivedInvocations.append((allowedMessageTypes: allowedMessageTypes, roomProxy: roomProxy, timelineItemFactory: timelineItemFactory, mediaProvider: mediaProvider))
}
if let buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderClosure = buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderClosure {
return await buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderClosure(allowedMessageTypes, roomProxy, timelineItemFactory, mediaProvider)
} else {
return buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderReturnValue
}
}
}
class RoomTimelineProviderMock: RoomTimelineProviderProtocol, @unchecked Sendable {
var updatePublisher: AnyPublisher<([TimelineItemProxy], PaginationState), Never> {
get { return underlyingUpdatePublisher }
set(value) { underlyingUpdatePublisher = value }
}
var underlyingUpdatePublisher: AnyPublisher<([TimelineItemProxy], PaginationState), Never>!
var itemProxies: [TimelineItemProxy] = []
var paginationState: PaginationState {
get { return underlyingPaginationState }
set(value) { underlyingPaginationState = value }
}
var underlyingPaginationState: PaginationState!
var kind: TimelineKind {
get { return underlyingKind }
set(value) { underlyingKind = value }
}
var underlyingKind: TimelineKind!
var membershipChangePublisher: AnyPublisher<Void, Never> {
get { return underlyingMembershipChangePublisher }
set(value) { underlyingMembershipChangePublisher = value }
}
var underlyingMembershipChangePublisher: AnyPublisher<Void, Never>!
}
class SecureBackupControllerMock: SecureBackupControllerProtocol, @unchecked Sendable {
var recoveryState: CurrentValuePublisher<SecureBackupRecoveryState, Never> {
get { return underlyingRecoveryState }
@@ -14651,12 +14414,249 @@ class SessionVerificationControllerProxyMock: SessionVerificationControllerProxy
}
}
}
class TimelineControllerFactoryMock: TimelineControllerFactoryProtocol, @unchecked Sendable {
//MARK: - buildTimelineController
var buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderUnderlyingCallsCount = 0
var buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderCallsCount: Int {
get {
if Thread.isMainThread {
return buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderUnderlyingCallsCount = newValue
}
}
}
}
var buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderCalled: Bool {
return buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderCallsCount > 0
}
var buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderReceivedArguments: (roomProxy: JoinedRoomProxyProtocol, initialFocussedEventID: String?, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol)?
var buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderReceivedInvocations: [(roomProxy: JoinedRoomProxyProtocol, initialFocussedEventID: String?, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol)] = []
var buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderUnderlyingReturnValue: TimelineControllerProtocol!
var buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderReturnValue: TimelineControllerProtocol! {
get {
if Thread.isMainThread {
return buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderUnderlyingReturnValue
} else {
var returnValue: TimelineControllerProtocol? = nil
DispatchQueue.main.sync {
returnValue = buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderUnderlyingReturnValue = newValue
}
}
}
}
var buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderClosure: ((JoinedRoomProxyProtocol, String?, RoomTimelineItemFactoryProtocol, MediaProviderProtocol) -> TimelineControllerProtocol)?
func buildTimelineController(roomProxy: JoinedRoomProxyProtocol, initialFocussedEventID: String?, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol) -> TimelineControllerProtocol {
buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderCallsCount += 1
buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderReceivedArguments = (roomProxy: roomProxy, initialFocussedEventID: initialFocussedEventID, timelineItemFactory: timelineItemFactory, mediaProvider: mediaProvider)
DispatchQueue.main.async {
self.buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderReceivedInvocations.append((roomProxy: roomProxy, initialFocussedEventID: initialFocussedEventID, timelineItemFactory: timelineItemFactory, mediaProvider: mediaProvider))
}
if let buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderClosure = buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderClosure {
return buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderClosure(roomProxy, initialFocussedEventID, timelineItemFactory, mediaProvider)
} else {
return buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderReturnValue
}
}
//MARK: - buildPinnedEventsTimelineController
var buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount = 0
var buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderCallsCount: Int {
get {
if Thread.isMainThread {
return buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount = newValue
}
}
}
}
var buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderCalled: Bool {
return buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderCallsCount > 0
}
var buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReceivedArguments: (roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol)?
var buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReceivedInvocations: [(roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol)] = []
var buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue: TimelineControllerProtocol?
var buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReturnValue: TimelineControllerProtocol? {
get {
if Thread.isMainThread {
return buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue
} else {
var returnValue: TimelineControllerProtocol?? = nil
DispatchQueue.main.sync {
returnValue = buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue = newValue
}
}
}
}
var buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderClosure: ((JoinedRoomProxyProtocol, RoomTimelineItemFactoryProtocol, MediaProviderProtocol) async -> TimelineControllerProtocol?)?
func buildPinnedEventsTimelineController(roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol) async -> TimelineControllerProtocol? {
buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderCallsCount += 1
buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReceivedArguments = (roomProxy: roomProxy, timelineItemFactory: timelineItemFactory, mediaProvider: mediaProvider)
DispatchQueue.main.async {
self.buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReceivedInvocations.append((roomProxy: roomProxy, timelineItemFactory: timelineItemFactory, mediaProvider: mediaProvider))
}
if let buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderClosure = buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderClosure {
return await buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderClosure(roomProxy, timelineItemFactory, mediaProvider)
} else {
return buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReturnValue
}
}
//MARK: - buildMessageFilteredTimelineController
var buildMessageFilteredTimelineControllerAllowedMessageTypesPresentationRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount = 0
var buildMessageFilteredTimelineControllerAllowedMessageTypesPresentationRoomProxyTimelineItemFactoryMediaProviderCallsCount: Int {
get {
if Thread.isMainThread {
return buildMessageFilteredTimelineControllerAllowedMessageTypesPresentationRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = buildMessageFilteredTimelineControllerAllowedMessageTypesPresentationRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
buildMessageFilteredTimelineControllerAllowedMessageTypesPresentationRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
buildMessageFilteredTimelineControllerAllowedMessageTypesPresentationRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount = newValue
}
}
}
}
var buildMessageFilteredTimelineControllerAllowedMessageTypesPresentationRoomProxyTimelineItemFactoryMediaProviderCalled: Bool {
return buildMessageFilteredTimelineControllerAllowedMessageTypesPresentationRoomProxyTimelineItemFactoryMediaProviderCallsCount > 0
}
var buildMessageFilteredTimelineControllerAllowedMessageTypesPresentationRoomProxyTimelineItemFactoryMediaProviderReceivedArguments: (allowedMessageTypes: [RoomMessageEventMessageType], presentation: TimelineKind.MediaPresentation, roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol)?
var buildMessageFilteredTimelineControllerAllowedMessageTypesPresentationRoomProxyTimelineItemFactoryMediaProviderReceivedInvocations: [(allowedMessageTypes: [RoomMessageEventMessageType], presentation: TimelineKind.MediaPresentation, roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol)] = []
var buildMessageFilteredTimelineControllerAllowedMessageTypesPresentationRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue: Result<TimelineControllerProtocol, TimelineFactoryControllerError>!
var buildMessageFilteredTimelineControllerAllowedMessageTypesPresentationRoomProxyTimelineItemFactoryMediaProviderReturnValue: Result<TimelineControllerProtocol, TimelineFactoryControllerError>! {
get {
if Thread.isMainThread {
return buildMessageFilteredTimelineControllerAllowedMessageTypesPresentationRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue
} else {
var returnValue: Result<TimelineControllerProtocol, TimelineFactoryControllerError>? = nil
DispatchQueue.main.sync {
returnValue = buildMessageFilteredTimelineControllerAllowedMessageTypesPresentationRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
buildMessageFilteredTimelineControllerAllowedMessageTypesPresentationRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
buildMessageFilteredTimelineControllerAllowedMessageTypesPresentationRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue = newValue
}
}
}
}
var buildMessageFilteredTimelineControllerAllowedMessageTypesPresentationRoomProxyTimelineItemFactoryMediaProviderClosure: (([RoomMessageEventMessageType], TimelineKind.MediaPresentation, JoinedRoomProxyProtocol, RoomTimelineItemFactoryProtocol, MediaProviderProtocol) async -> Result<TimelineControllerProtocol, TimelineFactoryControllerError>)?
func buildMessageFilteredTimelineController(allowedMessageTypes: [RoomMessageEventMessageType], presentation: TimelineKind.MediaPresentation, roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol) async -> Result<TimelineControllerProtocol, TimelineFactoryControllerError> {
buildMessageFilteredTimelineControllerAllowedMessageTypesPresentationRoomProxyTimelineItemFactoryMediaProviderCallsCount += 1
buildMessageFilteredTimelineControllerAllowedMessageTypesPresentationRoomProxyTimelineItemFactoryMediaProviderReceivedArguments = (allowedMessageTypes: allowedMessageTypes, presentation: presentation, roomProxy: roomProxy, timelineItemFactory: timelineItemFactory, mediaProvider: mediaProvider)
DispatchQueue.main.async {
self.buildMessageFilteredTimelineControllerAllowedMessageTypesPresentationRoomProxyTimelineItemFactoryMediaProviderReceivedInvocations.append((allowedMessageTypes: allowedMessageTypes, presentation: presentation, roomProxy: roomProxy, timelineItemFactory: timelineItemFactory, mediaProvider: mediaProvider))
}
if let buildMessageFilteredTimelineControllerAllowedMessageTypesPresentationRoomProxyTimelineItemFactoryMediaProviderClosure = buildMessageFilteredTimelineControllerAllowedMessageTypesPresentationRoomProxyTimelineItemFactoryMediaProviderClosure {
return await buildMessageFilteredTimelineControllerAllowedMessageTypesPresentationRoomProxyTimelineItemFactoryMediaProviderClosure(allowedMessageTypes, presentation, roomProxy, timelineItemFactory, mediaProvider)
} else {
return buildMessageFilteredTimelineControllerAllowedMessageTypesPresentationRoomProxyTimelineItemFactoryMediaProviderReturnValue
}
}
}
class TimelineProviderMock: TimelineProviderProtocol, @unchecked Sendable {
var updatePublisher: AnyPublisher<([TimelineItemProxy], PaginationState), Never> {
get { return underlyingUpdatePublisher }
set(value) { underlyingUpdatePublisher = value }
}
var underlyingUpdatePublisher: AnyPublisher<([TimelineItemProxy], PaginationState), Never>!
var itemProxies: [TimelineItemProxy] = []
var paginationState: PaginationState {
get { return underlyingPaginationState }
set(value) { underlyingPaginationState = value }
}
var underlyingPaginationState: PaginationState!
var kind: TimelineKind {
get { return underlyingKind }
set(value) { underlyingKind = value }
}
var underlyingKind: TimelineKind!
var membershipChangePublisher: AnyPublisher<Void, Never> {
get { return underlyingMembershipChangePublisher }
set(value) { underlyingMembershipChangePublisher = value }
}
var underlyingMembershipChangePublisher: AnyPublisher<Void, Never>!
}
class TimelineProxyMock: TimelineProxyProtocol, @unchecked Sendable {
var timelineProvider: RoomTimelineProviderProtocol {
var timelineProvider: TimelineProviderProtocol {
get { return underlyingTimelineProvider }
set(value) { underlyingTimelineProvider = value }
}
var underlyingTimelineProvider: RoomTimelineProviderProtocol!
var underlyingTimelineProvider: TimelineProviderProtocol!
//MARK: - subscribeForUpdates
@@ -1,24 +0,0 @@
//
// Copyright 2022-2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//
import Foundation
struct RoomTimelineControllerFactoryMockConfiguration {
var timelineController: RoomTimelineControllerProtocol?
}
extension RoomTimelineControllerFactoryMock {
convenience init(configuration: RoomTimelineControllerFactoryMockConfiguration) {
self.init()
buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderReturnValue = configuration.timelineController ?? {
let timelineController = MockRoomTimelineController()
timelineController.timelineItems = RoomTimelineItemFixtures.largeChunk
return timelineController
}()
}
}
@@ -0,0 +1,24 @@
//
// Copyright 2022-2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//
import Foundation
extension TimelineControllerFactoryMock {
struct Configuration {
var timelineController: TimelineControllerProtocol?
}
convenience init(_ configuration: Configuration) {
self.init()
buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderReturnValue = configuration.timelineController ?? {
let timelineController = MockTimelineController()
timelineController.timelineItems = RoomTimelineItemFixtures.largeChunk
return timelineController
}()
}
}
@@ -10,7 +10,7 @@ import Foundation
import MatrixRustSDK
@MainActor
class AutoUpdatingRoomTimelineProviderMock: RoomTimelineProvider {
class AutoUpdatingTimelineProviderMock: TimelineProvider {
static var timelineListener: TimelineListener?
private let innerPaginationStatePublisher: PassthroughSubject<PaginationState, Never>
@@ -24,9 +24,9 @@ extension TimelineProxyMock {
sendReadReceiptForTypeReturnValue = .success(())
if configuration.isAutoUpdating {
underlyingTimelineProvider = AutoUpdatingRoomTimelineProviderMock()
underlyingTimelineProvider = AutoUpdatingTimelineProviderMock()
} else {
let timelineProvider = RoomTimelineProviderMock()
let timelineProvider = TimelineProviderMock()
timelineProvider.paginationState = .init(backward: configuration.timelineStartReached ? .timelineEndReached : .idle, forward: .timelineEndReached)
timelineProvider.underlyingMembershipChangePublisher = PassthroughSubject().eraseToAnyPublisher()
underlyingTimelineProvider = timelineProvider
@@ -163,7 +163,7 @@ struct TimelineMediaPreviewModifier_Previews: PreviewProvider {
thumbnailSource: nil,
contentType: .pdf))
let timelineController = MockRoomTimelineController(timelineKind: .media(.mediaFilesScreen))
let timelineController = MockTimelineController(timelineKind: .media(.mediaFilesScreen))
timelineController.timelineItems = [item]
let mediaProvider = MediaProviderMock(configuration: .init())
@@ -223,7 +223,7 @@ struct TimelineMediaPreviewDetailsView_Previews: PreviewProvider, TestablePrevie
contentType: contentType))
let timelineKind = TimelineKind.media(isPresentedOnRoomScreen ? .roomScreen : .mediaFilesScreen)
let timelineController = MockRoomTimelineController(timelineKind: timelineKind)
let timelineController = MockTimelineController(timelineKind: timelineKind)
timelineController.timelineItems = [item]
let viewModel = TimelineMediaPreviewViewModel(initialItem: item,
@@ -144,7 +144,7 @@ struct TimelineMediaPreviewRedactConfirmationView_Previews: PreviewProvider, Tes
thumbnailInfo: .mockThumbnail,
contentType: contentType))
let timelineController = MockRoomTimelineController(timelineKind: .media(.mediaFilesScreen))
let timelineController = MockTimelineController(timelineKind: .media(.mediaFilesScreen))
timelineController.timelineItems = [item]
return TimelineMediaPreviewViewModel(initialItem: item,
timelineViewModel: TimelineViewModel.mock(timelineKind: timelineController.timelineKind,
@@ -10,14 +10,15 @@ import SwiftUI
struct MediaEventsTimelineScreenCoordinatorParameters {
let roomProxy: JoinedRoomProxyProtocol
let mediaTimelineController: RoomTimelineControllerProtocol
let filesTimelineController: RoomTimelineControllerProtocol
let mediaTimelineController: TimelineControllerProtocol
let filesTimelineController: TimelineControllerProtocol
let mediaProvider: MediaProviderProtocol
let mediaPlayerProvider: MediaPlayerProviderProtocol
let voiceMessageMediaManager: VoiceMessageMediaManagerProtocol
let appMediator: AppMediatorProtocol
let emojiProvider: EmojiProviderProtocol
let userIndicatorController: UserIndicatorControllerProtocol
let timelineControllerFactory: TimelineControllerFactoryProtocol
}
enum MediaEventsTimelineScreenCoordinatorAction {
@@ -47,7 +48,8 @@ final class MediaEventsTimelineScreenCoordinator: CoordinatorProtocol {
appMediator: parameters.appMediator,
appSettings: ServiceLocator.shared.settings,
analyticsService: ServiceLocator.shared.analytics,
emojiProvider: parameters.emojiProvider)
emojiProvider: parameters.emojiProvider,
timelineControllerFactory: parameters.timelineControllerFactory)
let filesTimelineViewModel = TimelineViewModel(roomProxy: parameters.roomProxy,
timelineController: parameters.filesTimelineController,
@@ -58,7 +60,8 @@ final class MediaEventsTimelineScreenCoordinator: CoordinatorProtocol {
appMediator: parameters.appMediator,
appSettings: ServiceLocator.shared.settings,
analyticsService: ServiceLocator.shared.analytics,
emojiProvider: parameters.emojiProvider)
emojiProvider: parameters.emojiProvider,
timelineControllerFactory: parameters.timelineControllerFactory)
viewModel = MediaEventsTimelineScreenViewModel(mediaTimelineViewModel: mediaTimelineViewModel,
filesTimelineViewModel: filesTimelineViewModel,
@@ -259,9 +259,9 @@ struct MediaEventsTimelineScreen_Previews: PreviewProvider, TestablePreview {
private static func makeTimelineViewModel(empty: Bool) -> TimelineViewModel {
let timelineController = if empty {
MockRoomTimelineController.emptyMediaGallery
MockTimelineController.emptyMediaGallery
} else {
MockRoomTimelineController.mediaGallery
MockTimelineController.mediaGallery
}
return TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Preview room")),
@@ -273,6 +273,7 @@ struct MediaEventsTimelineScreen_Previews: PreviewProvider, TestablePreview {
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
analyticsService: ServiceLocator.shared.analytics,
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings))
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
}
}
@@ -10,12 +10,13 @@ import SwiftUI
struct PinnedEventsTimelineScreenCoordinatorParameters {
let roomProxy: JoinedRoomProxyProtocol
let timelineController: RoomTimelineControllerProtocol
let timelineController: TimelineControllerProtocol
let mediaProvider: MediaProviderProtocol
let mediaPlayerProvider: MediaPlayerProviderProtocol
let voiceMessageMediaManager: VoiceMessageMediaManagerProtocol
let appMediator: AppMediatorProtocol
let emojiProvider: EmojiProviderProtocol
let timelineControllerFactory: TimelineControllerFactoryProtocol
}
enum PinnedEventsTimelineScreenCoordinatorAction {
@@ -51,7 +52,8 @@ final class PinnedEventsTimelineScreenCoordinator: CoordinatorProtocol {
appMediator: parameters.appMediator,
appSettings: ServiceLocator.shared.settings,
analyticsService: ServiceLocator.shared.analytics,
emojiProvider: parameters.emojiProvider)
emojiProvider: parameters.emojiProvider,
timelineControllerFactory: parameters.timelineControllerFactory)
}
func start() {
@@ -87,7 +87,7 @@ struct PinnedEventsTimelineScreen: View {
struct PinnedEventsTimelineScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = PinnedEventsTimelineScreenViewModel(analyticsService: ServiceLocator.shared.analytics)
static let emptyTimelineViewModel: TimelineViewModel = {
let timelineController = MockRoomTimelineController(timelineKind: .pinned)
let timelineController = MockTimelineController(timelineKind: .pinned)
timelineController.timelineItems = []
return TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Preview room")),
timelineController: timelineController,
@@ -98,7 +98,8 @@ struct PinnedEventsTimelineScreen_Previews: PreviewProvider, TestablePreview {
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
analyticsService: ServiceLocator.shared.analytics,
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings))
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
}()
static var previews: some View {
@@ -21,7 +21,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
private let appSettings: AppSettings
private var dmRecipient: RoomMemberProxyProtocol?
private var pinnedEventsTimelineProvider: RoomTimelineProviderProtocol? {
private var pinnedEventsTimelineProvider: TimelineProviderProtocol? {
didSet {
guard let pinnedEventsTimelineProvider else {
return
@@ -10,7 +10,7 @@ import SwiftUI
struct RoomPollsHistoryScreenCoordinatorParameters {
let pollInteractionHandler: PollInteractionHandlerProtocol
let roomTimelineController: RoomTimelineControllerProtocol
let timelineController: TimelineControllerProtocol
}
enum RoomPollsHistoryScreenCoordinatorAction {
@@ -28,7 +28,7 @@ final class RoomPollsHistoryScreenCoordinator: CoordinatorProtocol {
init(parameters: RoomPollsHistoryScreenCoordinatorParameters) {
viewModel = RoomPollsHistoryScreenViewModel(pollInteractionHandler: parameters.pollInteractionHandler,
roomTimelineController: parameters.roomTimelineController,
timelineController: parameters.timelineController,
userIndicatorController: ServiceLocator.shared.userIndicatorController)
}
@@ -18,7 +18,7 @@ class RoomPollsHistoryScreenViewModel: RoomPollsHistoryScreenViewModelType, Room
}
private let pollInteractionHandler: PollInteractionHandlerProtocol
private let roomTimelineController: RoomTimelineControllerProtocol
private let timelineController: TimelineControllerProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private var paginateBackwardsTask: Task<Void, Never>?
@@ -31,10 +31,10 @@ class RoomPollsHistoryScreenViewModel: RoomPollsHistoryScreenViewModelType, Room
}
init(pollInteractionHandler: PollInteractionHandlerProtocol,
roomTimelineController: RoomTimelineControllerProtocol,
timelineController: TimelineControllerProtocol,
userIndicatorController: UserIndicatorControllerProtocol) {
self.pollInteractionHandler = pollInteractionHandler
self.roomTimelineController = roomTimelineController
self.timelineController = timelineController
self.userIndicatorController = userIndicatorController
super.init(initialViewState: RoomPollsHistoryScreenViewState(title: L10n.screenPollsHistoryTitle,
@@ -65,7 +65,7 @@ class RoomPollsHistoryScreenViewModel: RoomPollsHistoryScreenViewModelType, Room
// MARK: - Private
private func setupSubscriptions() {
roomTimelineController.callbacks
timelineController.callbacks
.receive(on: DispatchQueue.main)
.sink { [weak self] callback in
guard let self else { return }
@@ -132,7 +132,7 @@ class RoomPollsHistoryScreenViewModel: RoomPollsHistoryScreenViewModelType, Room
private func updatePollsList(filter: RoomPollsHistoryFilter) {
// Get the poll timeline items to display
var items: [PollRoomTimelineItem] = []
for timelineItem in roomTimelineController.timelineItems {
for timelineItem in timelineController.timelineItems {
if let pollRoomTimelineItem = timelineItem as? PollRoomTimelineItem {
// Apply the filter
switch filter {
@@ -148,7 +148,7 @@ class RoomPollsHistoryScreenViewModel: RoomPollsHistoryScreenViewModelType, Room
// Map into RoomPollsHistoryPollDetails to have both the event timestamp and the timeline item
state.pollTimelineItems = items.map { item in
guard let timestamp = roomTimelineController.eventTimestamp(for: item.id) else {
guard let timestamp = timelineController.eventTimestamp(for: item.id) else {
return nil
}
return RoomPollsHistoryPollDetails(timestamp: timestamp, item: item)
@@ -167,7 +167,7 @@ class RoomPollsHistoryScreenViewModel: RoomPollsHistoryScreenViewModelType, Room
return
}
state.isBackPaginating = true
switch await roomTimelineController.paginateBackwards(requestSize: Constants.backPaginationEventLimit) {
switch await timelineController.paginateBackwards(requestSize: Constants.backPaginationEventLimit) {
case .failure(let error):
MXLog.error("failed to back paginate. \(error)")
default:
@@ -114,33 +114,33 @@ private extension DateFormatter {
struct RoomPollsHistoryScreen_Previews: PreviewProvider, TestablePreview {
static let viewModelEmpty: RoomPollsHistoryScreenViewModel = {
let roomTimelineController = MockRoomTimelineController()
roomTimelineController.timelineItems = []
let timelineController = MockTimelineController()
timelineController.timelineItems = []
let roomProxyMockConfiguration = JoinedRoomProxyMockConfiguration(name: "Polls")
let viewModel = RoomPollsHistoryScreenViewModel(pollInteractionHandler: PollInteractionHandlerMock(),
roomTimelineController: roomTimelineController,
timelineController: timelineController,
userIndicatorController: UserIndicatorControllerMock())
return viewModel
}()
static let viewModel: RoomPollsHistoryScreenViewModel = {
let roomTimelineController = MockRoomTimelineController()
let timelineController = MockTimelineController()
let polls = [PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: false)),
PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: true)),
PollRoomTimelineItem.mock(poll: .emptyDisclosed, isEditable: true)]
roomTimelineController.timelineItems = polls
timelineController.timelineItems = polls
for i in 0..<polls.count {
let item = polls[i]
let date: Date! = DateComponents(calendar: .current, timeZone: .gmt, year: 2023, month: 12, day: 1 + i, hour: 12).date
roomTimelineController.timelineItemsTimestamp[item.id] = date
timelineController.timelineItemsTimestamp[item.id] = date
}
let roomProxyMockConfiguration = JoinedRoomProxyMockConfiguration(name: "Polls", timelineStartReached: true)
let viewModel = RoomPollsHistoryScreenViewModel(pollInteractionHandler: PollInteractionHandlerMock(),
roomTimelineController: roomTimelineController,
timelineController: timelineController,
userIndicatorController: UserIndicatorControllerMock())
return viewModel
@@ -16,7 +16,7 @@ struct RoomScreenCoordinatorParameters {
let roomProxy: JoinedRoomProxyProtocol
var focussedEvent: FocusEvent?
var sharedText: String?
let timelineController: RoomTimelineControllerProtocol
let timelineController: TimelineControllerProtocol
let mediaProvider: MediaProviderProtocol
let mediaPlayerProvider: MediaPlayerProviderProtocol
let voiceMessageMediaManager: VoiceMessageMediaManagerProtocol
@@ -26,6 +26,7 @@ struct RoomScreenCoordinatorParameters {
let appMediator: AppMediatorProtocol
let appSettings: AppSettings
let composerDraftService: ComposerDraftServiceProtocol
let timelineControllerFactory: TimelineControllerFactoryProtocol
}
enum RoomScreenCoordinatorAction {
@@ -84,7 +85,8 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
appMediator: parameters.appMediator,
appSettings: parameters.appSettings,
analyticsService: ServiceLocator.shared.analytics,
emojiProvider: parameters.emojiProvider)
emojiProvider: parameters.emojiProvider,
timelineControllerFactory: parameters.timelineControllerFactory)
wysiwygViewModel = WysiwygComposerViewModel(minHeight: ComposerConstant.minHeight,
maxCompressedHeight: ComposerConstant.maxHeight,
@@ -32,7 +32,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
actionsSubject.eraseToAnyPublisher()
}
private var pinnedEventsTimelineProvider: RoomTimelineProviderProtocol? {
private var pinnedEventsTimelineProvider: TimelineProviderProtocol? {
didSet {
guard let pinnedEventsTimelineProvider else {
return
@@ -256,7 +256,7 @@ struct RoomScreen_Previews: PreviewProvider, TestablePreview {
hasOngoingCall: true))
static let roomViewModel = RoomScreenViewModel.mock(roomProxyMock: roomProxyMock)
static let timelineViewModel = TimelineViewModel(roomProxy: roomProxyMock,
timelineController: MockRoomTimelineController(),
timelineController: MockTimelineController(),
mediaProvider: MediaProviderMock(configuration: .init()),
mediaPlayerProvider: MediaPlayerProviderMock(),
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
@@ -264,7 +264,8 @@ struct RoomScreen_Previews: PreviewProvider, TestablePreview {
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
analyticsService: ServiceLocator.shared.analytics,
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings))
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
static var previews: some View {
NavigationStack {
@@ -29,7 +29,7 @@ enum TimelineInteractionHandlerAction {
@MainActor
class TimelineInteractionHandler {
private let roomProxy: JoinedRoomProxyProtocol
private let timelineController: RoomTimelineControllerProtocol
private let timelineController: TimelineControllerProtocol
private let mediaProvider: MediaProviderProtocol
private let mediaPlayerProvider: MediaPlayerProviderProtocol
private let voiceMessageRecorder: VoiceMessageRecorderProtocol
@@ -38,6 +38,8 @@ class TimelineInteractionHandler {
private let appMediator: AppMediatorProtocol
private let appSettings: AppSettings
private let analyticsService: AnalyticsService
private let emojiProvider: EmojiProviderProtocol
private let timelineControllerFactory: TimelineControllerFactoryProtocol
private let pollInteractionHandler: PollInteractionHandlerProtocol
private let actionsSubject: PassthroughSubject<TimelineInteractionHandlerAction, Never> = .init()
@@ -54,7 +56,7 @@ class TimelineInteractionHandler {
private var resumeVoiceMessagePlaybackAfterScrubbing = false
init(roomProxy: JoinedRoomProxyProtocol,
timelineController: RoomTimelineControllerProtocol,
timelineController: TimelineControllerProtocol,
mediaProvider: MediaProviderProtocol,
mediaPlayerProvider: MediaPlayerProviderProtocol,
voiceMessageMediaManager: VoiceMessageMediaManagerProtocol,
@@ -62,7 +64,9 @@ class TimelineInteractionHandler {
userIndicatorController: UserIndicatorControllerProtocol,
appMediator: AppMediatorProtocol,
appSettings: AppSettings,
analyticsService: AnalyticsService) {
analyticsService: AnalyticsService,
emojiProvider: EmojiProviderProtocol,
timelineControllerFactory: TimelineControllerFactoryProtocol) {
self.roomProxy = roomProxy
self.timelineController = timelineController
self.mediaProvider = mediaProvider
@@ -73,6 +77,8 @@ class TimelineInteractionHandler {
self.appMediator = appMediator
self.appSettings = appSettings
self.analyticsService = analyticsService
self.emojiProvider = emojiProvider
self.timelineControllerFactory = timelineControllerFactory
pollInteractionHandler = PollInteractionHandler(analyticsService: analyticsService, roomProxy: roomProxy)
}
@@ -495,7 +501,7 @@ class TimelineInteractionHandler {
actionsSubject.send(.displayEmojiPicker(itemID: itemID, selectedEmojis: selectedEmojis))
}
func processItemTap(_ itemID: TimelineItemIdentifier) async -> RoomTimelineControllerAction {
func processItemTap(_ itemID: TimelineItemIdentifier) async -> TimelineControllerAction {
guard let timelineItem = timelineController.timelineItems.firstUsingStableID(itemID) else {
return .none
}
@@ -522,7 +528,7 @@ class TimelineInteractionHandler {
}
}
private func displayMediaActionIfPossible(timelineItem: RoomTimelineItemProtocol) async -> RoomTimelineControllerAction {
private func displayMediaActionIfPossible(timelineItem: RoomTimelineItemProtocol) async -> TimelineControllerAction {
var source: MediaSourceProxy?
var filename: String
var caption: String?
@@ -22,13 +22,14 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
}
private let roomProxy: JoinedRoomProxyProtocol
private let timelineController: RoomTimelineControllerProtocol
private let timelineController: TimelineControllerProtocol
private let mediaPlayerProvider: MediaPlayerProviderProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private let appMediator: AppMediatorProtocol
private let appSettings: AppSettings
private let analyticsService: AnalyticsService
private let emojiProvider: EmojiProviderProtocol
private let timelineControllerFactory: TimelineControllerFactoryProtocol
private let timelineInteractionHandler: TimelineInteractionHandler
@@ -44,7 +45,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
init(roomProxy: JoinedRoomProxyProtocol,
focussedEventID: String? = nil,
timelineController: RoomTimelineControllerProtocol,
timelineController: TimelineControllerProtocol,
mediaProvider: MediaProviderProtocol,
mediaPlayerProvider: MediaPlayerProviderProtocol,
voiceMessageMediaManager: VoiceMessageMediaManagerProtocol,
@@ -52,7 +53,8 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
appMediator: AppMediatorProtocol,
appSettings: AppSettings,
analyticsService: AnalyticsService,
emojiProvider: EmojiProviderProtocol) {
emojiProvider: EmojiProviderProtocol,
timelineControllerFactory: TimelineControllerFactoryProtocol) {
self.timelineController = timelineController
self.mediaPlayerProvider = mediaPlayerProvider
self.roomProxy = roomProxy
@@ -61,6 +63,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
self.userIndicatorController = userIndicatorController
self.appMediator = appMediator
self.emojiProvider = emojiProvider
self.timelineControllerFactory = timelineControllerFactory
let voiceMessageRecorder = VoiceMessageRecorder(audioRecorder: AudioRecorder(), mediaPlayerProvider: mediaPlayerProvider)
@@ -73,7 +76,9 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
userIndicatorController: userIndicatorController,
appMediator: appMediator,
appSettings: appSettings,
analyticsService: analyticsService)
analyticsService: analyticsService,
emojiProvider: emojiProvider,
timelineControllerFactory: timelineControllerFactory)
super.init(initialViewState: TimelineViewState(timelineKind: timelineController.timelineKind,
roomID: roomProxy.id,
@@ -865,10 +870,10 @@ private extension RoomInfoProxy {
extension TimelineViewModel {
static let mock = mock(timelineKind: .live)
static func mock(timelineKind: TimelineKind = .live, timelineController: MockRoomTimelineController? = nil) -> TimelineViewModel {
static func mock(timelineKind: TimelineKind = .live, timelineController: MockTimelineController? = nil) -> TimelineViewModel {
TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Preview room")),
focussedEventID: nil,
timelineController: timelineController ?? MockRoomTimelineController(timelineKind: timelineKind),
timelineController: timelineController ?? MockTimelineController(timelineKind: timelineKind),
mediaProvider: MediaProviderMock(configuration: .init()),
mediaPlayerProvider: MediaPlayerProviderMock(),
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
@@ -876,7 +881,8 @@ extension TimelineViewModel {
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
analyticsService: ServiceLocator.shared.analytics,
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings))
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
}
}
@@ -44,7 +44,7 @@ struct ReadReceiptsSummaryView_Previews: PreviewProvider, TestablePreview {
]
let roomProxyMock = JoinedRoomProxyMock(.init(name: "Room", members: members))
let mock = TimelineViewModel(roomProxy: roomProxyMock,
timelineController: MockRoomTimelineController(),
timelineController: MockTimelineController(),
mediaProvider: MediaProviderMock(configuration: .init()),
mediaPlayerProvider: MediaPlayerProviderMock(),
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
@@ -52,7 +52,8 @@ struct ReadReceiptsSummaryView_Previews: PreviewProvider, TestablePreview {
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
analyticsService: ServiceLocator.shared.analytics,
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings))
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
return mock
}()
@@ -330,7 +330,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
let roomProxy = JoinedRoomProxyMock(.init(name: "Preview Room", pinnedEventIDs: ["pinned"]))
return TimelineViewModel(roomProxy: roomProxy,
focussedEventID: nil,
timelineController: MockRoomTimelineController(),
timelineController: MockTimelineController(),
mediaProvider: MediaProviderMock(configuration: .init()),
mediaPlayerProvider: MediaPlayerProviderMock(),
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
@@ -338,7 +338,8 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
analyticsService: ServiceLocator.shared.analytics,
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings))
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
}()
static var previews: some View {
@@ -79,7 +79,7 @@ struct TimelineReadReceiptsView_Previews: PreviewProvider, TestablePreview {
]
static let viewModel = TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Test", members: members)),
timelineController: MockRoomTimelineController(),
timelineController: MockTimelineController(),
mediaProvider: MediaProviderMock(configuration: .init()),
mediaPlayerProvider: MediaPlayerProviderMock(),
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
@@ -87,7 +87,8 @@ struct TimelineReadReceiptsView_Previews: PreviewProvider, TestablePreview {
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
analyticsService: ServiceLocator.shared.analytics,
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings))
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
static let singleReceipt = [ReadReceipt(userID: RoomMemberProxyMock.mockAlice.userID, formattedTimestamp: "Now")]
static let doubleReceipt = [ReadReceipt(userID: RoomMemberProxyMock.mockAlice.userID, formattedTimestamp: "Now"),
@@ -89,7 +89,7 @@ struct HighlightedTimelineItemTimeline_Previews: PreviewProvider {
static let focussedEventID = "RoomTimelineItemFixtures.default.5"
static let timelineViewModel = TimelineViewModel(roomProxy: roomProxyMock,
focussedEventID: focussedEventID,
timelineController: MockRoomTimelineController(),
timelineController: MockTimelineController(),
mediaProvider: MediaProviderMock(configuration: .init()),
mediaPlayerProvider: MediaPlayerProviderMock(),
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
@@ -97,7 +97,8 @@ struct HighlightedTimelineItemTimeline_Previews: PreviewProvider {
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
analyticsService: ServiceLocator.shared.analytics,
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings))
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
static var previews: some View {
NavigationStack {
@@ -82,7 +82,7 @@ struct TimelineView_Previews: PreviewProvider, TestablePreview {
name: "Preview room"))
static let roomViewModel = RoomScreenViewModel.mock(roomProxyMock: roomProxyMock)
static let timelineViewModel = TimelineViewModel(roomProxy: roomProxyMock,
timelineController: MockRoomTimelineController(),
timelineController: MockTimelineController(),
mediaProvider: MediaProviderMock(configuration: .init()),
mediaPlayerProvider: MediaPlayerProviderMock(),
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
@@ -90,7 +90,8 @@ struct TimelineView_Previews: PreviewProvider, TestablePreview {
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
analyticsService: ServiceLocator.shared.analytics,
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings))
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
static var previews: some View {
NavigationStack {
@@ -182,14 +182,15 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
}
}
func messageFilteredTimeline(allowedMessageTypes: [RoomMessageEventMessageType]) async -> Result<any TimelineProxyProtocol, RoomProxyError> {
func messageFilteredTimeline(allowedMessageTypes: [RoomMessageEventMessageType],
presentation: TimelineKind.MediaPresentation) async -> Result<any TimelineProxyProtocol, RoomProxyError> {
do {
let sdkTimeline = try await room.timelineWithConfiguration(configuration: .init(focus: .live,
allowedMessageTypes: .only(types: allowedMessageTypes),
internalIdPrefix: nil,
dateDividerMode: .monthly))
let timeline = TimelineProxy(timeline: sdkTimeline, kind: .media(.mediaFilesScreen))
let timeline = TimelineProxy(timeline: sdkTimeline, kind: .media(presentation))
await timeline.subscribeForUpdates()
return .success(timeline)
@@ -78,7 +78,8 @@ protocol JoinedRoomProxyProtocol: RoomProxyProtocol {
func timelineFocusedOnEvent(eventID: String, numberOfEvents: UInt16) async -> Result<TimelineProxyProtocol, RoomProxyError>
func messageFilteredTimeline(allowedMessageTypes: [RoomMessageEventMessageType]) async -> Result<TimelineProxyProtocol, RoomProxyError>
func messageFilteredTimeline(allowedMessageTypes: [RoomMessageEventMessageType],
presentation: TimelineKind.MediaPresentation) async -> Result<TimelineProxyProtocol, RoomProxyError>
func enableEncryption() async -> Result<Void, RoomProxyError>
@@ -11,7 +11,7 @@ import Combine
import Foundation
import MatrixRustSDK
class MockRoomTimelineController: RoomTimelineControllerProtocol {
class MockTimelineController: TimelineControllerProtocol {
/// An array of timeline item arrays that will be inserted in order for each back pagination request.
var backPaginationResponses: [[RoomTimelineItemProtocol]] = []
/// An array of timeline items that will be appended in order when ``simulateIncomingItems()`` is called.
@@ -21,7 +21,7 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
var roomID: String { roomProxy?.id ?? "MockRoomIdentifier" }
var timelineKind: TimelineKind
let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()
let callbacks = PassthroughSubject<TimelineControllerCallback, Never>()
var paginationState: PaginationState = .initial {
didSet {
@@ -34,14 +34,14 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
private var client: UITestsSignalling.Client?
static var mediaGallery: MockRoomTimelineController {
MockRoomTimelineController(timelineKind: .media(.mediaFilesScreen), timelineItems: (0..<5).reduce([]) { partialResult, _ in
static var mediaGallery: MockTimelineController {
MockTimelineController(timelineKind: .media(.mediaFilesScreen), timelineItems: (0..<5).reduce([]) { partialResult, _ in
partialResult + [RoomTimelineItemFixtures.separator] + RoomTimelineItemFixtures.mediaChunk
})
}
static var emptyMediaGallery: MockRoomTimelineController {
let mock = MockRoomTimelineController(timelineKind: .media(.mediaFilesScreen))
static var emptyMediaGallery: MockTimelineController {
let mock = MockTimelineController(timelineKind: .media(.mediaFilesScreen))
mock.paginationState = PaginationState(backward: .timelineEndReached, forward: .timelineEndReached)
return mock
}
@@ -63,7 +63,7 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
}
private(set) var focusOnEventCallCount = 0
func focusOnEvent(_ eventID: String, timelineSize: UInt16) async -> Result<Void, RoomTimelineControllerError> {
func focusOnEvent(_ eventID: String, timelineSize: UInt16) async -> Result<Void, TimelineControllerError> {
focusOnEventCallCount += 1
callbacks.send(.isLive(false))
return .success(())
@@ -75,7 +75,7 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
callbacks.send(.isLive(true))
}
func paginateBackwards(requestSize: UInt16) async -> Result<Void, RoomTimelineControllerError> {
func paginateBackwards(requestSize: UInt16) async -> Result<Void, TimelineControllerError> {
paginationState = PaginationState(backward: .paginating, forward: .timelineEndReached)
if client == nil {
@@ -85,7 +85,7 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
return .success(())
}
func paginateForwards(requestSize: UInt16) async -> Result<Void, RoomTimelineControllerError> {
func paginateForwards(requestSize: UInt16) async -> Result<Void, TimelineControllerError> {
// try? await simulateForwardPagination()
.success(())
}
@@ -1,55 +0,0 @@
//
// Copyright 2022-2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//
import Foundation
import MatrixRustSDK
struct RoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol {
func buildRoomTimelineController(roomProxy: JoinedRoomProxyProtocol,
initialFocussedEventID: String?,
timelineItemFactory: RoomTimelineItemFactoryProtocol,
mediaProvider: MediaProviderProtocol) -> RoomTimelineControllerProtocol {
RoomTimelineController(roomProxy: roomProxy,
timelineProxy: roomProxy.timeline,
initialFocussedEventID: initialFocussedEventID,
timelineItemFactory: timelineItemFactory,
mediaProvider: mediaProvider,
appSettings: ServiceLocator.shared.settings)
}
func buildPinnedEventsRoomTimelineController(roomProxy: JoinedRoomProxyProtocol,
timelineItemFactory: RoomTimelineItemFactoryProtocol,
mediaProvider: MediaProviderProtocol) async -> RoomTimelineControllerProtocol? {
guard let pinnedEventsTimeline = await roomProxy.pinnedEventsTimeline else {
return nil
}
return RoomTimelineController(roomProxy: roomProxy,
timelineProxy: pinnedEventsTimeline,
initialFocussedEventID: nil,
timelineItemFactory: timelineItemFactory,
mediaProvider: mediaProvider,
appSettings: ServiceLocator.shared.settings)
}
func buildMessageFilteredRoomTimelineController(allowedMessageTypes: [RoomMessageEventMessageType],
roomProxy: JoinedRoomProxyProtocol,
timelineItemFactory: RoomTimelineItemFactoryProtocol,
mediaProvider: MediaProviderProtocol) async -> Result<RoomTimelineControllerProtocol, RoomTimelineFactoryControllerError> {
switch await roomProxy.messageFilteredTimeline(allowedMessageTypes: allowedMessageTypes) {
case .success(let timelineProxy):
return .success(RoomTimelineController(roomProxy: roomProxy,
timelineProxy: timelineProxy,
initialFocussedEventID: nil,
timelineItemFactory: timelineItemFactory,
mediaProvider: mediaProvider,
appSettings: ServiceLocator.shared.settings))
case .failure(let error):
return .failure(.roomProxyError(error))
}
}
}
@@ -1,33 +0,0 @@
//
// Copyright 2022-2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//
import Foundation
import MatrixRustSDK
enum RoomTimelineFactoryControllerError: Error {
case roomProxyError(RoomProxyError)
}
@MainActor
protocol RoomTimelineControllerFactoryProtocol {
func buildRoomTimelineController(roomProxy: JoinedRoomProxyProtocol,
initialFocussedEventID: String?,
timelineItemFactory: RoomTimelineItemFactoryProtocol,
mediaProvider: MediaProviderProtocol) -> RoomTimelineControllerProtocol
func buildPinnedEventsRoomTimelineController(roomProxy: JoinedRoomProxyProtocol,
timelineItemFactory: RoomTimelineItemFactoryProtocol,
mediaProvider: MediaProviderProtocol) async -> RoomTimelineControllerProtocol?
func buildMessageFilteredRoomTimelineController(allowedMessageTypes: [RoomMessageEventMessageType],
roomProxy: JoinedRoomProxyProtocol,
timelineItemFactory: RoomTimelineItemFactoryProtocol,
mediaProvider: MediaProviderProtocol) async -> Result<RoomTimelineControllerProtocol, RoomTimelineFactoryControllerError>
}
// sourcery: AutoMockable
extension RoomTimelineControllerFactoryProtocol { }
@@ -11,19 +11,19 @@ import IntentsUI
import MatrixRustSDK
import UIKit
class RoomTimelineController: RoomTimelineControllerProtocol {
class TimelineController: TimelineControllerProtocol {
private let roomProxy: JoinedRoomProxyProtocol
private let liveTimelineProvider: RoomTimelineProviderProtocol
private let liveTimelineProvider: TimelineProviderProtocol
private let timelineItemFactory: RoomTimelineItemFactoryProtocol
private let mediaProvider: MediaProviderProtocol
private let appSettings: AppSettings
private let serialDispatchQueue: DispatchQueue
let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()
let callbacks = PassthroughSubject<TimelineControllerCallback, Never>()
private var activeTimeline: TimelineProxyProtocol
private var activeTimelineProvider: RoomTimelineProviderProtocol {
private var activeTimelineProvider: TimelineProviderProtocol {
didSet {
configureActiveTimelineProvider()
}
@@ -57,7 +57,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
self.mediaProvider = mediaProvider
self.appSettings = appSettings
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomtimelineprovider", qos: .utility)
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.timelineprovider", qos: .utility)
activeTimeline = timelineProxy
activeTimelineProvider = liveTimelineProvider
@@ -82,7 +82,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
}
}
func focusOnEvent(_ eventID: String, timelineSize: UInt16) async -> Result<Void, RoomTimelineControllerError> {
func focusOnEvent(_ eventID: String, timelineSize: UInt16) async -> Result<Void, TimelineControllerError> {
switch await roomProxy.timelineFocusedOnEvent(eventID: eventID, numberOfEvents: timelineSize) {
case .success(let timeline):
await timeline.subscribeForUpdates()
@@ -103,7 +103,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
activeTimelineProvider = liveTimelineProvider
}
func paginateBackwards(requestSize: UInt16) async -> Result<Void, RoomTimelineControllerError> {
func paginateBackwards(requestSize: UInt16) async -> Result<Void, TimelineControllerError> {
MXLog.info("Started back pagination request")
switch await activeTimeline.paginateBackwards(requestSize: requestSize) {
case .success:
@@ -115,7 +115,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
}
}
func paginateForwards(requestSize: UInt16) async -> Result<Void, RoomTimelineControllerError> {
func paginateForwards(requestSize: UInt16) async -> Result<Void, TimelineControllerError> {
MXLog.info("Started forward pagination request")
switch await activeTimeline.paginateForwards(requestSize: requestSize) {
case .success:
@@ -0,0 +1,56 @@
//
// Copyright 2022-2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//
import Foundation
import MatrixRustSDK
struct TimelineControllerFactory: TimelineControllerFactoryProtocol {
func buildTimelineController(roomProxy: JoinedRoomProxyProtocol,
initialFocussedEventID: String?,
timelineItemFactory: RoomTimelineItemFactoryProtocol,
mediaProvider: MediaProviderProtocol) -> TimelineControllerProtocol {
TimelineController(roomProxy: roomProxy,
timelineProxy: roomProxy.timeline,
initialFocussedEventID: initialFocussedEventID,
timelineItemFactory: timelineItemFactory,
mediaProvider: mediaProvider,
appSettings: ServiceLocator.shared.settings)
}
func buildPinnedEventsTimelineController(roomProxy: JoinedRoomProxyProtocol,
timelineItemFactory: RoomTimelineItemFactoryProtocol,
mediaProvider: MediaProviderProtocol) async -> TimelineControllerProtocol? {
guard let pinnedEventsTimeline = await roomProxy.pinnedEventsTimeline else {
return nil
}
return TimelineController(roomProxy: roomProxy,
timelineProxy: pinnedEventsTimeline,
initialFocussedEventID: nil,
timelineItemFactory: timelineItemFactory,
mediaProvider: mediaProvider,
appSettings: ServiceLocator.shared.settings)
}
func buildMessageFilteredTimelineController(allowedMessageTypes: [RoomMessageEventMessageType],
presentation: TimelineKind.MediaPresentation,
roomProxy: JoinedRoomProxyProtocol,
timelineItemFactory: RoomTimelineItemFactoryProtocol,
mediaProvider: MediaProviderProtocol) async -> Result<TimelineControllerProtocol, TimelineFactoryControllerError> {
switch await roomProxy.messageFilteredTimeline(allowedMessageTypes: allowedMessageTypes, presentation: presentation) {
case .success(let timelineProxy):
return .success(TimelineController(roomProxy: roomProxy,
timelineProxy: timelineProxy,
initialFocussedEventID: nil,
timelineItemFactory: timelineItemFactory,
mediaProvider: mediaProvider,
appSettings: ServiceLocator.shared.settings))
case .failure(let error):
return .failure(.roomProxyError(error))
}
}
}
@@ -0,0 +1,34 @@
//
// Copyright 2022-2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//
import Foundation
import MatrixRustSDK
enum TimelineFactoryControllerError: Error {
case roomProxyError(RoomProxyError)
}
@MainActor
protocol TimelineControllerFactoryProtocol {
func buildTimelineController(roomProxy: JoinedRoomProxyProtocol,
initialFocussedEventID: String?,
timelineItemFactory: RoomTimelineItemFactoryProtocol,
mediaProvider: MediaProviderProtocol) -> TimelineControllerProtocol
func buildPinnedEventsTimelineController(roomProxy: JoinedRoomProxyProtocol,
timelineItemFactory: RoomTimelineItemFactoryProtocol,
mediaProvider: MediaProviderProtocol) async -> TimelineControllerProtocol?
func buildMessageFilteredTimelineController(allowedMessageTypes: [RoomMessageEventMessageType],
presentation: TimelineKind.MediaPresentation,
roomProxy: JoinedRoomProxyProtocol,
timelineItemFactory: RoomTimelineItemFactoryProtocol,
mediaProvider: MediaProviderProtocol) async -> Result<TimelineControllerProtocol, TimelineFactoryControllerError>
}
// sourcery: AutoMockable
extension TimelineControllerFactoryProtocol { }
@@ -9,25 +9,25 @@ import Combine
import MatrixRustSDK
import SwiftUI
enum RoomTimelineControllerCallback {
enum TimelineControllerCallback {
case updatedTimelineItems(timelineItems: [RoomTimelineItemProtocol], isSwitchingTimelines: Bool)
case paginationState(PaginationState)
case isLive(Bool)
}
enum RoomTimelineControllerAction {
enum TimelineControllerAction {
case displayMediaFile(file: MediaFileHandleProxy, title: String?)
case displayLocation(body: String, geoURI: GeoURI, description: String?)
case none
}
enum RoomTimelineControllerError: Error {
enum TimelineControllerError: Error {
case generic
case eventNotFound
}
@MainActor
protocol RoomTimelineControllerProtocol {
protocol TimelineControllerProtocol {
var roomID: String { get }
var timelineKind: TimelineKind { get }
@@ -37,17 +37,17 @@ protocol RoomTimelineControllerProtocol {
/// The current pagination state, use only for setting up the intial state
var paginationState: PaginationState { get }
var callbacks: PassthroughSubject<RoomTimelineControllerCallback, Never> { get }
var callbacks: PassthroughSubject<TimelineControllerCallback, Never> { get }
func processItemAppearance(_ itemID: TimelineItemIdentifier) async
func processItemDisappearance(_ itemID: TimelineItemIdentifier) async
func focusOnEvent(_ eventID: String, timelineSize: UInt16) async -> Result<Void, RoomTimelineControllerError>
func focusOnEvent(_ eventID: String, timelineSize: UInt16) async -> Result<Void, TimelineControllerError>
func focusLive()
func paginateBackwards(requestSize: UInt16) async -> Result<Void, RoomTimelineControllerError>
func paginateForwards(requestSize: UInt16) async -> Result<Void, RoomTimelineControllerError>
func paginateBackwards(requestSize: UInt16) async -> Result<Void, TimelineControllerError>
func paginateForwards(requestSize: UInt16) async -> Result<Void, TimelineControllerError>
func sendReadReceipt(for itemID: TimelineItemIdentifier) async
@@ -9,7 +9,7 @@ import Combine
import Foundation
import MatrixRustSDK
class RoomTimelineProvider: RoomTimelineProviderProtocol {
class TimelineProvider: TimelineProviderProtocol {
private var cancellables = Set<AnyCancellable>()
private let serialDispatchQueue: DispatchQueue
@@ -46,7 +46,7 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
}
init(timeline: Timeline, kind: TimelineKind, paginationStatePublisher: AnyPublisher<PaginationState, Never>) {
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomtimelineprovider", qos: .utility)
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.timelineprovider", qos: .utility)
itemProxiesSubject = CurrentValueSubject<[TimelineItemProxy], Never>([])
self.kind = kind
@@ -29,7 +29,7 @@ struct PaginationState: Equatable {
@MainActor
// sourcery: AutoMockable
protocol RoomTimelineProviderProtocol {
protocol TimelineProviderProtocol {
/// 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.
@@ -20,8 +20,8 @@ final class TimelineProxy: TimelineProxyProtocol {
private let kind: TimelineKind
private var innerTimelineProvider: RoomTimelineProviderProtocol!
var timelineProvider: RoomTimelineProviderProtocol {
private var innerTimelineProvider: TimelineProviderProtocol!
var timelineProvider: TimelineProviderProtocol {
innerTimelineProvider
}
@@ -47,7 +47,7 @@ final class TimelineProxy: TimelineProxyProtocol {
await subscribeToPagination()
let provider = await RoomTimelineProvider(timeline: timeline, kind: kind, paginationStatePublisher: paginationStatePublisher)
let provider = await TimelineProvider(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()
@@ -27,7 +27,7 @@ enum TimelineProxyError: Error {
// sourcery: AutoMockable
protocol TimelineProxyProtocol {
var timelineProvider: RoomTimelineProviderProtocol { get }
var timelineProvider: TimelineProviderProtocol { get }
func subscribeForUpdates() async
@@ -235,7 +235,7 @@ class MockScreen: Identifiable {
let navigationStackCoordinator = NavigationStackCoordinator()
let parameters = RoomScreenCoordinatorParameters(clientProxy: ClientProxyMock(),
roomProxy: JoinedRoomProxyMock(.init(name: "Some room name", avatarURL: nil)),
timelineController: MockRoomTimelineController(),
timelineController: MockTimelineController(),
mediaProvider: MediaProviderMock(configuration: .init()),
mediaPlayerProvider: MediaPlayerProviderMock(),
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
@@ -244,13 +244,14 @@ class MockScreen: Identifiable {
ongoingCallRoomIDPublisher: .init(.init(nil)),
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
composerDraftService: ComposerDraftServiceMock(.init()))
composerDraftService: ComposerDraftServiceMock(.init()),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
let coordinator = RoomScreenCoordinator(parameters: parameters)
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .roomSmallTimeline:
let navigationStackCoordinator = NavigationStackCoordinator()
let timelineController = MockRoomTimelineController()
let timelineController = MockTimelineController()
timelineController.timelineItems = RoomTimelineItemFixtures.smallChunk
let parameters = RoomScreenCoordinatorParameters(clientProxy: ClientProxyMock(),
roomProxy: JoinedRoomProxyMock(.init(name: "New room", avatarURL: .mockMXCAvatar)),
@@ -263,13 +264,14 @@ class MockScreen: Identifiable {
ongoingCallRoomIDPublisher: .init(.init(nil)),
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
composerDraftService: ComposerDraftServiceMock(.init()))
composerDraftService: ComposerDraftServiceMock(.init()),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
let coordinator = RoomScreenCoordinator(parameters: parameters)
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .roomSmallTimelineWithReactions:
let navigationStackCoordinator = NavigationStackCoordinator()
let timelineController = MockRoomTimelineController()
let timelineController = MockTimelineController()
timelineController.timelineItems = RoomTimelineItemFixtures.default
let parameters = RoomScreenCoordinatorParameters(clientProxy: ClientProxyMock(),
roomProxy: JoinedRoomProxyMock(.init(name: "New room", avatarURL: .mockMXCAvatar)),
@@ -282,13 +284,14 @@ class MockScreen: Identifiable {
ongoingCallRoomIDPublisher: .init(.init(nil)),
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
composerDraftService: ComposerDraftServiceMock(.init()))
composerDraftService: ComposerDraftServiceMock(.init()),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
let coordinator = RoomScreenCoordinator(parameters: parameters)
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .roomSmallTimelineWithReadReceipts:
let navigationStackCoordinator = NavigationStackCoordinator()
let timelineController = MockRoomTimelineController()
let timelineController = MockTimelineController()
timelineController.timelineItems = RoomTimelineItemFixtures.smallChunkWithReadReceipts
let parameters = RoomScreenCoordinatorParameters(clientProxy: ClientProxyMock(),
roomProxy: JoinedRoomProxyMock(.init(name: "New room", avatarURL: .mockMXCAvatar)),
@@ -301,14 +304,15 @@ class MockScreen: Identifiable {
ongoingCallRoomIDPublisher: .init(.init(nil)),
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
composerDraftService: ComposerDraftServiceMock(.init()))
composerDraftService: ComposerDraftServiceMock(.init()),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
let coordinator = RoomScreenCoordinator(parameters: parameters)
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .roomSmallTimelineIncomingAndSmallPagination:
let navigationStackCoordinator = NavigationStackCoordinator()
let timelineController = MockRoomTimelineController(listenForSignals: true)
let timelineController = MockTimelineController(listenForSignals: true)
timelineController.timelineItems = RoomTimelineItemFixtures.smallChunk
timelineController.backPaginationResponses = [RoomTimelineItemFixtures.singleMessageChunk]
timelineController.incomingItems = [RoomTimelineItemFixtures.incomingMessage]
@@ -323,7 +327,8 @@ class MockScreen: Identifiable {
ongoingCallRoomIDPublisher: .init(.init(nil)),
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
composerDraftService: ComposerDraftServiceMock(.init()))
composerDraftService: ComposerDraftServiceMock(.init()),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
let coordinator = RoomScreenCoordinator(parameters: parameters)
navigationStackCoordinator.setRootCoordinator(coordinator)
@@ -331,7 +336,7 @@ class MockScreen: Identifiable {
case .roomSmallTimelineLargePagination:
let navigationStackCoordinator = NavigationStackCoordinator()
let timelineController = MockRoomTimelineController(listenForSignals: true)
let timelineController = MockTimelineController(listenForSignals: true)
timelineController.timelineItems = RoomTimelineItemFixtures.smallChunk
timelineController.backPaginationResponses = [RoomTimelineItemFixtures.largeChunk]
let parameters = RoomScreenCoordinatorParameters(clientProxy: ClientProxyMock(),
@@ -345,7 +350,8 @@ class MockScreen: Identifiable {
ongoingCallRoomIDPublisher: .init(.init(nil)),
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
composerDraftService: ComposerDraftServiceMock(.init()))
composerDraftService: ComposerDraftServiceMock(.init()),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
let coordinator = RoomScreenCoordinator(parameters: parameters)
navigationStackCoordinator.setRootCoordinator(coordinator)
@@ -353,7 +359,7 @@ class MockScreen: Identifiable {
case .roomLayoutTop:
let navigationStackCoordinator = NavigationStackCoordinator()
let timelineController = MockRoomTimelineController(listenForSignals: true)
let timelineController = MockTimelineController(listenForSignals: true)
timelineController.timelineItems = RoomTimelineItemFixtures.largeChunk
timelineController.backPaginationResponses = [RoomTimelineItemFixtures.largeChunk]
let parameters = RoomScreenCoordinatorParameters(clientProxy: ClientProxyMock(),
@@ -367,7 +373,8 @@ class MockScreen: Identifiable {
ongoingCallRoomIDPublisher: .init(.init(nil)),
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
composerDraftService: ComposerDraftServiceMock(.init()))
composerDraftService: ComposerDraftServiceMock(.init()),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
let coordinator = RoomScreenCoordinator(parameters: parameters)
navigationStackCoordinator.setRootCoordinator(coordinator)
@@ -375,7 +382,7 @@ class MockScreen: Identifiable {
case .roomLayoutMiddle:
let navigationStackCoordinator = NavigationStackCoordinator()
let timelineController = MockRoomTimelineController(listenForSignals: true)
let timelineController = MockTimelineController(listenForSignals: true)
timelineController.timelineItems = RoomTimelineItemFixtures.largeChunk
timelineController.backPaginationResponses = [RoomTimelineItemFixtures.largeChunk]
timelineController.incomingItems = [RoomTimelineItemFixtures.incomingMessage]
@@ -390,7 +397,8 @@ class MockScreen: Identifiable {
ongoingCallRoomIDPublisher: .init(.init(nil)),
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
composerDraftService: ComposerDraftServiceMock(.init()))
composerDraftService: ComposerDraftServiceMock(.init()),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
let coordinator = RoomScreenCoordinator(parameters: parameters)
navigationStackCoordinator.setRootCoordinator(coordinator)
@@ -398,7 +406,7 @@ class MockScreen: Identifiable {
case .roomLayoutBottom:
let navigationStackCoordinator = NavigationStackCoordinator()
let timelineController = MockRoomTimelineController(listenForSignals: true)
let timelineController = MockTimelineController(listenForSignals: true)
timelineController.timelineItems = RoomTimelineItemFixtures.largeChunk
timelineController.incomingItems = [RoomTimelineItemFixtures.incomingMessage]
let parameters = RoomScreenCoordinatorParameters(clientProxy: ClientProxyMock(),
@@ -412,7 +420,8 @@ class MockScreen: Identifiable {
ongoingCallRoomIDPublisher: .init(.init(nil)),
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
composerDraftService: ComposerDraftServiceMock(.init()))
composerDraftService: ComposerDraftServiceMock(.init()),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
let coordinator = RoomScreenCoordinator(parameters: parameters)
navigationStackCoordinator.setRootCoordinator(coordinator)
@@ -420,7 +429,7 @@ class MockScreen: Identifiable {
case .roomLayoutHighlight:
let navigationStackCoordinator = NavigationStackCoordinator()
let timelineController = MockRoomTimelineController()
let timelineController = MockTimelineController()
timelineController.timelineItems = RoomTimelineItemFixtures.permalinkChunk
let parameters = RoomScreenCoordinatorParameters(clientProxy: ClientProxyMock(),
roomProxy: JoinedRoomProxyMock(.init(name: "Timeline highlight", avatarURL: .mockMXCAvatar)),
@@ -433,7 +442,8 @@ class MockScreen: Identifiable {
ongoingCallRoomIDPublisher: .init(.init(nil)),
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
composerDraftService: ComposerDraftServiceMock(.init()))
composerDraftService: ComposerDraftServiceMock(.init()),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
let coordinator = RoomScreenCoordinator(parameters: parameters)
navigationStackCoordinator.setRootCoordinator(coordinator)
@@ -454,7 +464,7 @@ class MockScreen: Identifiable {
case .roomWithDisclosedPolls:
let navigationStackCoordinator = NavigationStackCoordinator()
let timelineController = MockRoomTimelineController()
let timelineController = MockTimelineController()
timelineController.timelineItems = RoomTimelineItemFixtures.disclosedPolls
timelineController.incomingItems = []
let parameters = RoomScreenCoordinatorParameters(clientProxy: ClientProxyMock(),
@@ -468,7 +478,8 @@ class MockScreen: Identifiable {
ongoingCallRoomIDPublisher: .init(.init(nil)),
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
composerDraftService: ComposerDraftServiceMock(.init()))
composerDraftService: ComposerDraftServiceMock(.init()),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
let coordinator = RoomScreenCoordinator(parameters: parameters)
navigationStackCoordinator.setRootCoordinator(coordinator)
@@ -476,7 +487,7 @@ class MockScreen: Identifiable {
case .roomWithUndisclosedPolls:
let navigationStackCoordinator = NavigationStackCoordinator()
let timelineController = MockRoomTimelineController()
let timelineController = MockTimelineController()
timelineController.timelineItems = RoomTimelineItemFixtures.undisclosedPolls
timelineController.incomingItems = []
let parameters = RoomScreenCoordinatorParameters(clientProxy: ClientProxyMock(),
@@ -490,7 +501,8 @@ class MockScreen: Identifiable {
ongoingCallRoomIDPublisher: .init(.init(nil)),
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
composerDraftService: ComposerDraftServiceMock(.init()))
composerDraftService: ComposerDraftServiceMock(.init()),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
let coordinator = RoomScreenCoordinator(parameters: parameters)
navigationStackCoordinator.setRootCoordinator(coordinator)
@@ -498,7 +510,7 @@ class MockScreen: Identifiable {
case .roomWithOutgoingPolls:
let navigationStackCoordinator = NavigationStackCoordinator()
let timelineController = MockRoomTimelineController()
let timelineController = MockTimelineController()
timelineController.timelineItems = RoomTimelineItemFixtures.outgoingPolls
timelineController.incomingItems = []
let parameters = RoomScreenCoordinatorParameters(clientProxy: ClientProxyMock(),
@@ -512,7 +524,8 @@ class MockScreen: Identifiable {
ongoingCallRoomIDPublisher: .init(.init(nil)),
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
composerDraftService: ComposerDraftServiceMock(.init()))
composerDraftService: ComposerDraftServiceMock(.init()),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
let coordinator = RoomScreenCoordinator(parameters: parameters)
navigationStackCoordinator.setRootCoordinator(coordinator)
@@ -540,7 +553,7 @@ class MockScreen: Identifiable {
appSettings: ServiceLocator.shared.settings),
bugReportService: BugReportServiceMock(),
elementCallService: ElementCallServiceMock(.init()),
roomTimelineControllerFactory: RoomTimelineControllerFactoryMock(configuration: .init()),
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
appMediator: appMediator,
appSettings: appSettings,
appHooks: AppHooks(),
@@ -678,14 +691,14 @@ class MockScreen: Identifiable {
clientProxy.roomForIdentifierReturnValue = .joined(roomProxy)
let timelineController = RoomTimelineController(roomProxy: roomProxy,
timelineProxy: roomProxy.timeline,
initialFocussedEventID: nil,
timelineItemFactory: RoomTimelineItemFactory(userID: "@alice:matrix.org",
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
stateEventStringBuilder: RoomStateEventStringBuilder(userID: "@alice:matrix.org")),
mediaProvider: MediaProviderMock(configuration: .init()),
appSettings: ServiceLocator.shared.settings)
let timelineController = TimelineController(roomProxy: roomProxy,
timelineProxy: roomProxy.timeline,
initialFocussedEventID: nil,
timelineItemFactory: RoomTimelineItemFactory(userID: "@alice:matrix.org",
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
stateEventStringBuilder: RoomStateEventStringBuilder(userID: "@alice:matrix.org")),
mediaProvider: MediaProviderMock(configuration: .init()),
appSettings: ServiceLocator.shared.settings)
let flowCoordinator = UserSessionFlowCoordinator(userSession: UserSessionMock(.init(clientProxy: clientProxy)),
navigationRootCoordinator: navigationRootCoordinator,
@@ -693,7 +706,7 @@ class MockScreen: Identifiable {
appSettings: ServiceLocator.shared.settings),
bugReportService: BugReportServiceMock(),
elementCallService: ElementCallServiceMock(.init()),
roomTimelineControllerFactory: RoomTimelineControllerFactoryMock(configuration: .init(timelineController: timelineController)),
timelineControllerFactory: TimelineControllerFactoryMock(.init(timelineController: timelineController)),
appMediator: AppMediatorMock.default,
appSettings: appSettings,
appHooks: AppHooks(),