Move the pinned timeline in line with the other timeline creation methods and have it report errors just like the others

This commit is contained in:
Stefan Ceriu
2025-05-21 22:16:03 +03:00
committed by Stefan Ceriu
parent 909aa086f2
commit 250a7ed451
13 changed files with 180 additions and 121 deletions
@@ -68,9 +68,9 @@ class PinnedEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)) stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID))
guard let timelineController = await timelineControllerFactory.buildPinnedEventsTimelineController(roomProxy: roomProxy, guard case let .success(timelineController) = await timelineControllerFactory.buildPinnedEventsTimelineController(roomProxy: roomProxy,
timelineItemFactory: timelineItemFactory, timelineItemFactory: timelineItemFactory,
mediaProvider: userSession.mediaProvider) else { 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") fatalError("This can never fail because we allow this view to be presented only when the timeline is fully loaded and not nil")
} }
@@ -6270,23 +6270,6 @@ class JoinedRoomProxyMock: JoinedRoomProxyProtocol, @unchecked Sendable {
set(value) { underlyingTimeline = value } set(value) { underlyingTimeline = value }
} }
var underlyingTimeline: TimelineProxyProtocol! var underlyingTimeline: TimelineProxyProtocol!
var pinnedEventsTimelineCallsCount = 0
var pinnedEventsTimelineCalled: Bool {
return pinnedEventsTimelineCallsCount > 0
}
var pinnedEventsTimeline: TimelineProxyProtocol? {
get async {
pinnedEventsTimelineCallsCount += 1
if let pinnedEventsTimelineClosure = pinnedEventsTimelineClosure {
return await pinnedEventsTimelineClosure()
} else {
return underlyingPinnedEventsTimeline
}
}
}
var underlyingPinnedEventsTimeline: TimelineProxyProtocol?
var pinnedEventsTimelineClosure: (() async -> TimelineProxyProtocol?)?
var id: String { var id: String {
get { return underlyingId } get { return underlyingId }
set(value) { underlyingId = value } set(value) { underlyingId = value }
@@ -6578,6 +6561,70 @@ class JoinedRoomProxyMock: JoinedRoomProxyProtocol, @unchecked Sendable {
return messageFilteredTimelineFocusAllowedMessageTypesPresentationReturnValue return messageFilteredTimelineFocusAllowedMessageTypesPresentationReturnValue
} }
} }
//MARK: - pinnedEventsTimeline
var pinnedEventsTimelineUnderlyingCallsCount = 0
var pinnedEventsTimelineCallsCount: Int {
get {
if Thread.isMainThread {
return pinnedEventsTimelineUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = pinnedEventsTimelineUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
pinnedEventsTimelineUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
pinnedEventsTimelineUnderlyingCallsCount = newValue
}
}
}
}
var pinnedEventsTimelineCalled: Bool {
return pinnedEventsTimelineCallsCount > 0
}
var pinnedEventsTimelineUnderlyingReturnValue: Result<TimelineProxyProtocol, RoomProxyError>!
var pinnedEventsTimelineReturnValue: Result<TimelineProxyProtocol, RoomProxyError>! {
get {
if Thread.isMainThread {
return pinnedEventsTimelineUnderlyingReturnValue
} else {
var returnValue: Result<TimelineProxyProtocol, RoomProxyError>? = nil
DispatchQueue.main.sync {
returnValue = pinnedEventsTimelineUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
pinnedEventsTimelineUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
pinnedEventsTimelineUnderlyingReturnValue = newValue
}
}
}
}
var pinnedEventsTimelineClosure: (() async -> Result<TimelineProxyProtocol, RoomProxyError>)?
func pinnedEventsTimeline() async -> Result<TimelineProxyProtocol, RoomProxyError> {
pinnedEventsTimelineCallsCount += 1
if let pinnedEventsTimelineClosure = pinnedEventsTimelineClosure {
return await pinnedEventsTimelineClosure()
} else {
return pinnedEventsTimelineReturnValue
}
}
//MARK: - enableEncryption //MARK: - enableEncryption
var enableEncryptionUnderlyingCallsCount = 0 var enableEncryptionUnderlyingCallsCount = 0
@@ -15141,13 +15188,13 @@ class TimelineControllerFactoryMock: TimelineControllerFactoryProtocol, @uncheck
var buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReceivedArguments: (roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol)? var buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReceivedArguments: (roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol)?
var buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReceivedInvocations: [(roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol)] = [] var buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReceivedInvocations: [(roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol)] = []
var buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue: TimelineControllerProtocol? var buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue: Result<TimelineControllerProtocol, TimelineFactoryControllerError>!
var buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReturnValue: TimelineControllerProtocol? { var buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReturnValue: Result<TimelineControllerProtocol, TimelineFactoryControllerError>! {
get { get {
if Thread.isMainThread { if Thread.isMainThread {
return buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue return buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue
} else { } else {
var returnValue: TimelineControllerProtocol?? = nil var returnValue: Result<TimelineControllerProtocol, TimelineFactoryControllerError>? = nil
DispatchQueue.main.sync { DispatchQueue.main.sync {
returnValue = buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue returnValue = buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue
} }
@@ -15165,9 +15212,9 @@ class TimelineControllerFactoryMock: TimelineControllerFactoryProtocol, @uncheck
} }
} }
} }
var buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderClosure: ((JoinedRoomProxyProtocol, RoomTimelineItemFactoryProtocol, MediaProviderProtocol) async -> TimelineControllerProtocol?)? var buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderClosure: ((JoinedRoomProxyProtocol, RoomTimelineItemFactoryProtocol, MediaProviderProtocol) async -> Result<TimelineControllerProtocol, TimelineFactoryControllerError>)?
func buildPinnedEventsTimelineController(roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol) async -> TimelineControllerProtocol? { func buildPinnedEventsTimelineController(roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol) async -> Result<TimelineControllerProtocol, TimelineFactoryControllerError> {
buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderCallsCount += 1 buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderCallsCount += 1
buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReceivedArguments = (roomProxy: roomProxy, timelineItemFactory: timelineItemFactory, mediaProvider: mediaProvider) buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReceivedArguments = (roomProxy: roomProxy, timelineItemFactory: timelineItemFactory, mediaProvider: mediaProvider)
DispatchQueue.main.async { DispatchQueue.main.async {
@@ -60,6 +60,8 @@ extension JoinedRoomProxyMock {
timeline = TimelineProxyMock(.init(isAutoUpdating: configuration.shouldUseAutoUpdatingTimeline, timeline = TimelineProxyMock(.init(isAutoUpdating: configuration.shouldUseAutoUpdatingTimeline,
timelineStartReached: configuration.timelineStartReached)) timelineStartReached: configuration.timelineStartReached))
pinnedEventsTimelineReturnValue = .failure(.failedCreatingPinnedTimeline)
ownUserID = configuration.ownUserID ownUserID = configuration.ownUserID
infoPublisher = CurrentValueSubject(.init(roomInfo: .init(configuration))).asCurrentValuePublisher() infoPublisher = CurrentValueSubject(.init(roomInfo: .init(configuration))).asCurrentValuePublisher()
@@ -445,12 +445,12 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
} }
Task { Task {
guard let timelineProvider = await roomProxy.pinnedEventsTimeline?.timelineProvider else { guard case let .success(pinnedEventsTimeline) = await roomProxy.pinnedEventsTimeline() else {
return return
} }
if pinnedEventsTimelineProvider == nil { if pinnedEventsTimelineProvider == nil {
pinnedEventsTimelineProvider = timelineProvider pinnedEventsTimelineProvider = pinnedEventsTimeline.timelineProvider
} }
} }
} }
@@ -353,12 +353,12 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
} }
Task { Task {
guard let timelineProvider = await roomProxy.pinnedEventsTimeline?.timelineProvider else { guard case let .success(pinnedEventsTimeline) = await roomProxy.pinnedEventsTimeline() else {
return return
} }
if pinnedEventsTimelineProvider == nil { if pinnedEventsTimelineProvider == nil {
pinnedEventsTimelineProvider = timelineProvider pinnedEventsTimelineProvider = pinnedEventsTimeline.timelineProvider
} }
} }
} }
@@ -11,13 +11,15 @@ import MatrixRustSDK
class BannedRoomProxy: BannedRoomProxyProtocol { class BannedRoomProxy: BannedRoomProxyProtocol {
private let roomListItem: RoomListItemProtocol private let roomListItem: RoomListItemProtocol
private let roomPreview: RoomPreviewProtocol private let roomPreview: RoomPreviewProtocol
let info: BaseRoomInfoProxyProtocol
let ownUserID: String
// A room identifier is constant and lazy stops it from being fetched // A room identifier is constant and lazy stops it from being fetched
// multiple times over FFI // multiple times over FFI
lazy var id = info.id lazy var id = info.id
let ownUserID: String
let info: BaseRoomInfoProxyProtocol
init(roomListItem: RoomListItemProtocol, init(roomListItem: RoomListItemProtocol,
roomPreview: RoomPreviewProtocol, roomPreview: RoomPreviewProtocol,
ownUserID: String) throws { ownUserID: String) throws {
@@ -12,14 +12,16 @@ import UIKit
class InvitedRoomProxy: InvitedRoomProxyProtocol { class InvitedRoomProxy: InvitedRoomProxyProtocol {
private let roomListItem: RoomListItemProtocol private let roomListItem: RoomListItemProtocol
private let roomPreview: RoomPreviewProtocol private let roomPreview: RoomPreviewProtocol
let info: BaseRoomInfoProxyProtocol
let ownUserID: String
let inviter: RoomMemberProxyProtocol?
// A room identifier is constant and lazy stops it from being fetched // A room identifier is constant and lazy stops it from being fetched
// multiple times over FFI // multiple times over FFI
lazy var id: String = info.id lazy var id: String = info.id
let ownUserID: String
let info: BaseRoomInfoProxyProtocol
let inviter: RoomMemberProxyProtocol?
init(roomListItem: RoomListItemProtocol, init(roomListItem: RoomListItemProtocol,
roomPreview: RoomPreviewProtocol, roomPreview: RoomPreviewProtocol,
ownUserID: String) async throws { ownUserID: String) async throws {
@@ -14,50 +14,6 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
private let roomListService: RoomListServiceProtocol private let roomListService: RoomListServiceProtocol
private let roomListItem: RoomListItemProtocol private let roomListItem: RoomListItemProtocol
private let room: RoomProtocol private let room: RoomProtocol
let timeline: TimelineProxyProtocol
private var innerPinnedEventsTimeline: TimelineProxyProtocol?
private var innerPinnedEventsTimelineTask: Task<TimelineProxyProtocol?, Never>?
var pinnedEventsTimeline: TimelineProxyProtocol? {
get async {
// Check if is already available.
if let innerPinnedEventsTimeline {
return innerPinnedEventsTimeline
// Otherwise check if there is already a task loading it, and wait for it.
} else if let innerPinnedEventsTimelineTask,
let value = await innerPinnedEventsTimelineTask.value {
return value
// Else create and store a new task to load it and wait for it.
} else {
let task = Task<TimelineProxyProtocol?, Never> { [weak self] in
guard let self else {
return nil
}
do {
let sdkTimeline = try await room.timelineWithConfiguration(configuration: .init(focus: .pinnedEvents(maxEventsToLoad: 100, maxConcurrentRequests: 10),
filter: .all,
internalIdPrefix: nil,
dateDividerMode: .daily,
trackReadReceipts: false,
reportUtds: true))
let timeline = TimelineProxy(timeline: sdkTimeline, kind: .pinned)
await timeline.subscribeForUpdates()
innerPinnedEventsTimeline = timeline
return timeline
} catch {
MXLog.error("Failed creating pinned events timeline with error: \(error)")
return nil
}
}
innerPinnedEventsTimelineTask = task
return await task.value
}
}
}
// periphery:ignore - required for instance retention in the rust codebase // periphery:ignore - required for instance retention in the rust codebase
private var roomInfoObservationToken: TaskHandle? private var roomInfoObservationToken: TaskHandle?
@@ -68,8 +24,19 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
// periphery:ignore - required for instance retention in the rust codebase // periphery:ignore - required for instance retention in the rust codebase
private var knockRequestsChangesObservationToken: TaskHandle? private var knockRequestsChangesObservationToken: TaskHandle?
private var innerPinnedEventsTimeline: TimelineProxyProtocol?
private var innerPinnedEventsTimelineTask: Task<Result<TimelineProxyProtocol, RoomProxyError>, Never>?
private var subscribedForUpdates = false private var subscribedForUpdates = false
// A room identifier is constant and lazy stops it from being fetched
// multiple times over FFI
lazy var id: String = room.id()
var ownUserID: String { room.ownUserId() }
let timeline: TimelineProxyProtocol
private let infoSubject: CurrentValueSubject<RoomInfoProxy, Never> private let infoSubject: CurrentValueSubject<RoomInfoProxy, Never>
var infoPublisher: CurrentValuePublisher<RoomInfoProxy, Never> { var infoPublisher: CurrentValuePublisher<RoomInfoProxy, Never> {
infoSubject.asCurrentValuePublisher() infoSubject.asCurrentValuePublisher()
@@ -95,12 +62,6 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
knockRequestsStateSubject.asCurrentValuePublisher() knockRequestsStateSubject.asCurrentValuePublisher()
} }
// A room identifier is constant and lazy stops it from being fetched
// multiple times over FFI
lazy var id: String = room.id()
var ownUserID: String { room.ownUserId() }
var info: RoomInfoProxy { infoSubject.value }
init(roomListService: RoomListServiceProtocol, init(roomListService: RoomListServiceProtocol,
roomListItem: RoomListItemProtocol, roomListItem: RoomListItemProtocol,
room: RoomProtocol) async throws { room: RoomProtocol) async throws {
@@ -111,7 +72,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
infoSubject = try await .init(RoomInfoProxy(roomInfo: room.roomInfo())) infoSubject = try await .init(RoomInfoProxy(roomInfo: room.roomInfo()))
timeline = try await TimelineProxy(timeline: room.timelineWithConfiguration(configuration: .init(focus: .live, timeline = try await TimelineProxy(timeline: room.timelineWithConfiguration(configuration: .init(focus: .live,
filter: .eventTypeFilter(filter: eventFilters), filter: .eventTypeFilter(filter: excludedEventsFilter),
internalIdPrefix: nil, internalIdPrefix: nil,
dateDividerMode: .daily, dateDividerMode: .daily,
trackReadReceipts: true, trackReadReceipts: true,
@@ -131,24 +92,6 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
} }
} }
private let eventFilters: TimelineEventTypeFilter = {
var stateEventFilters: [StateEventType] = [.roomAliases,
.roomCanonicalAlias,
.roomGuestAccess,
.roomHistoryVisibility,
.roomJoinRules,
.roomPinnedEvents,
.roomPowerLevels,
.roomServerAcl,
.roomTombstone,
.spaceChild,
.spaceParent,
.policyRuleRoom,
.policyRuleServer,
.policyRuleUser]
return .exclude(eventTypes: stateEventFilters.map { FilterTimelineEventType.state(eventType: $0) })
}()
func subscribeForUpdates() async { func subscribeForUpdates() async {
guard !subscribedForUpdates else { guard !subscribedForUpdates else {
MXLog.warning("Room already subscribed for updates") MXLog.warning("Room already subscribed for updates")
@@ -273,6 +216,43 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
} }
} }
func pinnedEventsTimeline() async -> Result<TimelineProxyProtocol, RoomProxyError> {
// Check if is already available.
if let innerPinnedEventsTimeline {
return .success(innerPinnedEventsTimeline)
// Otherwise check if there is already a task loading it, and wait for it.
} else if let innerPinnedEventsTimelineTask {
return await innerPinnedEventsTimelineTask.value
} else { // Else create and store a new task to load it and wait for it.
let task = Task<Result<TimelineProxyProtocol, RoomProxyError>, Never> { [weak self] in
guard let self else {
return .failure(.failedCreatingPinnedTimeline)
}
do {
let sdkTimeline = try await room.timelineWithConfiguration(configuration: .init(focus: .pinnedEvents(maxEventsToLoad: 100, maxConcurrentRequests: 10),
filter: .all,
internalIdPrefix: nil,
dateDividerMode: .daily,
trackReadReceipts: false,
reportUtds: true))
let timeline = TimelineProxy(timeline: sdkTimeline, kind: .pinned)
await timeline.subscribeForUpdates()
innerPinnedEventsTimeline = timeline
return .success(timeline)
} catch {
MXLog.error("Failed creating pinned events timeline with error: \(error)")
return .failure(.sdkError(error))
}
}
innerPinnedEventsTimelineTask = task
return await task.value
}
}
func enableEncryption() async -> Result<Void, RoomProxyError> { func enableEncryption() async -> Result<Void, RoomProxyError> {
do { do {
try await room.enableEncryption() try await room.enableEncryption()
@@ -856,6 +836,24 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
MXLog.error("Failed observing requests to join with error: \(error)") MXLog.error("Failed observing requests to join with error: \(error)")
} }
} }
private let excludedEventsFilter: TimelineEventTypeFilter = {
var stateEventFilters: [StateEventType] = [.roomAliases,
.roomCanonicalAlias,
.roomGuestAccess,
.roomHistoryVisibility,
.roomJoinRules,
.roomPinnedEvents,
.roomPowerLevels,
.roomServerAcl,
.roomTombstone,
.spaceChild,
.spaceParent,
.policyRuleRoom,
.policyRuleServer,
.policyRuleUser]
return .exclude(eventTypes: stateEventFilters.map { FilterTimelineEventType.state(eventType: $0) })
}()
} }
private final class RoomTypingNotificationUpdateListener: TypingNotificationsListener { private final class RoomTypingNotificationUpdateListener: TypingNotificationsListener {
@@ -11,13 +11,15 @@ import MatrixRustSDK
class KnockedRoomProxy: KnockedRoomProxyProtocol { class KnockedRoomProxy: KnockedRoomProxyProtocol {
private let roomListItem: RoomListItemProtocol private let roomListItem: RoomListItemProtocol
private let roomPreview: RoomPreviewProtocol private let roomPreview: RoomPreviewProtocol
let info: BaseRoomInfoProxyProtocol
let ownUserID: String
// A room identifier is constant and lazy stops it from being fetched // A room identifier is constant and lazy stops it from being fetched
// multiple times over FFI // multiple times over FFI
lazy var id = info.id lazy var id = info.id
let ownUserID: String
let info: BaseRoomInfoProxyProtocol
init(roomListItem: RoomListItemProtocol, init(roomListItem: RoomListItemProtocol,
roomPreview: RoomPreviewProtocol, roomPreview: RoomPreviewProtocol,
ownUserID: String) throws { ownUserID: String) throws {
@@ -16,6 +16,7 @@ enum RoomProxyError: Error {
case invalidMedia case invalidMedia
case eventNotFound case eventNotFound
case missingTransactionID case missingTransactionID
case failedCreatingPinnedTimeline
} }
/// An enum that describes the relationship between the current user and the room, and contains a reference to the specific implementation of the `RoomProxy`. /// An enum that describes the relationship between the current user and the room, and contains a reference to the specific implementation of the `RoomProxy`.
@@ -75,8 +76,6 @@ protocol JoinedRoomProxyProtocol: RoomProxyProtocol {
var timeline: TimelineProxyProtocol { get } var timeline: TimelineProxyProtocol { get }
var pinnedEventsTimeline: TimelineProxyProtocol? { get async }
func subscribeForUpdates() async func subscribeForUpdates() async
func subscribeToRoomInfoUpdates() func subscribeToRoomInfoUpdates()
@@ -89,6 +88,8 @@ protocol JoinedRoomProxyProtocol: RoomProxyProtocol {
allowedMessageTypes: [TimelineAllowedMessageType], allowedMessageTypes: [TimelineAllowedMessageType],
presentation: TimelineKind.MediaPresentation) async -> Result<TimelineProxyProtocol, RoomProxyError> presentation: TimelineKind.MediaPresentation) async -> Result<TimelineProxyProtocol, RoomProxyError>
func pinnedEventsTimeline() async -> Result<TimelineProxyProtocol, RoomProxyError>
func enableEncryption() async -> Result<Void, RoomProxyError> func enableEncryption() async -> Result<Void, RoomProxyError>
func redact(_ eventID: String) async -> Result<Void, RoomProxyError> func redact(_ eventID: String) async -> Result<Void, RoomProxyError>
@@ -40,17 +40,18 @@ struct TimelineControllerFactory: TimelineControllerFactoryProtocol {
func buildPinnedEventsTimelineController(roomProxy: JoinedRoomProxyProtocol, func buildPinnedEventsTimelineController(roomProxy: JoinedRoomProxyProtocol,
timelineItemFactory: RoomTimelineItemFactoryProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol,
mediaProvider: MediaProviderProtocol) async -> TimelineControllerProtocol? { mediaProvider: MediaProviderProtocol) async -> Result<TimelineControllerProtocol, TimelineFactoryControllerError> {
guard let pinnedEventsTimeline = await roomProxy.pinnedEventsTimeline else { switch await roomProxy.pinnedEventsTimeline() {
return nil 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))
} }
return TimelineController(roomProxy: roomProxy,
timelineProxy: pinnedEventsTimeline,
initialFocussedEventID: nil,
timelineItemFactory: timelineItemFactory,
mediaProvider: mediaProvider,
appSettings: ServiceLocator.shared.settings)
} }
func buildMessageFilteredTimelineController(focus: TimelineFocus, func buildMessageFilteredTimelineController(focus: TimelineFocus,
@@ -26,7 +26,7 @@ protocol TimelineControllerFactoryProtocol {
func buildPinnedEventsTimelineController(roomProxy: JoinedRoomProxyProtocol, func buildPinnedEventsTimelineController(roomProxy: JoinedRoomProxyProtocol,
timelineItemFactory: RoomTimelineItemFactoryProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol,
mediaProvider: MediaProviderProtocol) async -> TimelineControllerProtocol? mediaProvider: MediaProviderProtocol) async -> Result<TimelineControllerProtocol, TimelineFactoryControllerError>
func buildMessageFilteredTimelineController(focus: TimelineFocus, func buildMessageFilteredTimelineController(focus: TimelineFocus,
allowedMessageTypes: [TimelineAllowedMessageType], allowedMessageTypes: [TimelineAllowedMessageType],
@@ -30,7 +30,11 @@ class RoomScreenViewModelTests: XCTestCase {
let roomProxyMock = JoinedRoomProxyMock(configuration) let roomProxyMock = JoinedRoomProxyMock(configuration)
// setup a way to inject the mock of the pinned events timeline // setup a way to inject the mock of the pinned events timeline
roomProxyMock.pinnedEventsTimelineClosure = { roomProxyMock.pinnedEventsTimelineClosure = {
await timelineSubject.values.first() guard let timeline = await timelineSubject.values.first() else {
fatalError()
}
return .success(timeline)
} }
// setup the room proxy actions publisher // setup the room proxy actions publisher
roomProxyMock.underlyingInfoPublisher = infoSubject.asCurrentValuePublisher() roomProxyMock.underlyingInfoPublisher = infoSubject.asCurrentValuePublisher()
@@ -113,7 +117,7 @@ class RoomScreenViewModelTests: XCTestCase {
pinnedTimelineProviderMock.itemProxies = [.event(.init(item: EventTimelineItem(configuration: .init(eventID: "test1")), uniqueID: .init("1"))), pinnedTimelineProviderMock.itemProxies = [.event(.init(item: EventTimelineItem(configuration: .init(eventID: "test1")), uniqueID: .init("1"))),
.event(.init(item: EventTimelineItem(configuration: .init(eventID: "test2")), uniqueID: .init("2"))), .event(.init(item: EventTimelineItem(configuration: .init(eventID: "test2")), uniqueID: .init("2"))),
.event(.init(item: EventTimelineItem(configuration: .init(eventID: "test3")), uniqueID: .init("3")))] .event(.init(item: EventTimelineItem(configuration: .init(eventID: "test3")), uniqueID: .init("3")))]
roomProxyMock.underlyingPinnedEventsTimeline = pinnedTimelineMock roomProxyMock.pinnedEventsTimelineReturnValue = .success(pinnedTimelineMock)
let viewModel = RoomScreenViewModel(clientProxy: ClientProxyMock(), let viewModel = RoomScreenViewModel(clientProxy: ClientProxyMock(),
roomProxy: roomProxyMock, roomProxy: roomProxyMock,
initialSelectedPinnedEventID: "test1", initialSelectedPinnedEventID: "test1",