mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 01:20:12 +00:00
TimelineKind refactor (#3193)
This commit is contained in:
@@ -11910,11 +11910,11 @@ class RoomTimelineProviderMock: RoomTimelineProviderProtocol {
|
|||||||
set(value) { underlyingPaginationState = value }
|
set(value) { underlyingPaginationState = value }
|
||||||
}
|
}
|
||||||
var underlyingPaginationState: PaginationState!
|
var underlyingPaginationState: PaginationState!
|
||||||
var isLive: Bool {
|
var kind: TimelineKind {
|
||||||
get { return underlyingIsLive }
|
get { return underlyingKind }
|
||||||
set(value) { underlyingIsLive = value }
|
set(value) { underlyingKind = value }
|
||||||
}
|
}
|
||||||
var underlyingIsLive: Bool!
|
var underlyingKind: TimelineKind!
|
||||||
var membershipChangePublisher: AnyPublisher<Void, Never> {
|
var membershipChangePublisher: AnyPublisher<Void, Never> {
|
||||||
get { return underlyingMembershipChangePublisher }
|
get { return underlyingMembershipChangePublisher }
|
||||||
set(value) { underlyingMembershipChangePublisher = value }
|
set(value) { underlyingMembershipChangePublisher = value }
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class AutoUpdatingRoomTimelineProviderMock: RoomTimelineProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
super.init(timeline: timelineMock,
|
super.init(timeline: timelineMock,
|
||||||
isLive: true,
|
kind: .live,
|
||||||
paginationStatePublisher: innerPaginationStatePublisher.eraseToAnyPublisher())
|
paginationStatePublisher: innerPaginationStatePublisher.eraseToAnyPublisher())
|
||||||
|
|
||||||
Task.detached {
|
Task.detached {
|
||||||
|
|||||||
-1
@@ -52,7 +52,6 @@ final class PinnedEventsTimelineScreenCoordinator: CoordinatorProtocol {
|
|||||||
viewModel = PinnedEventsTimelineScreenViewModel()
|
viewModel = PinnedEventsTimelineScreenViewModel()
|
||||||
timelineViewModel = TimelineViewModel(roomProxy: parameters.roomProxy,
|
timelineViewModel = TimelineViewModel(roomProxy: parameters.roomProxy,
|
||||||
timelineController: parameters.timelineController,
|
timelineController: parameters.timelineController,
|
||||||
isPinnedEventsTimeline: true,
|
|
||||||
mediaProvider: parameters.mediaProvider,
|
mediaProvider: parameters.mediaProvider,
|
||||||
mediaPlayerProvider: parameters.mediaPlayerProvider,
|
mediaPlayerProvider: parameters.mediaPlayerProvider,
|
||||||
voiceMessageMediaManager: parameters.voiceMessageMediaManager,
|
voiceMessageMediaManager: parameters.voiceMessageMediaManager,
|
||||||
|
|||||||
+1
-2
@@ -95,11 +95,10 @@ struct PinnedEventsTimelineScreen: View {
|
|||||||
struct PinnedEventsTimelineScreen_Previews: PreviewProvider, TestablePreview {
|
struct PinnedEventsTimelineScreen_Previews: PreviewProvider, TestablePreview {
|
||||||
static let viewModel = PinnedEventsTimelineScreenViewModel()
|
static let viewModel = PinnedEventsTimelineScreenViewModel()
|
||||||
static let emptyTimelineViewModel: TimelineViewModel = {
|
static let emptyTimelineViewModel: TimelineViewModel = {
|
||||||
let timelineController = MockRoomTimelineController()
|
let timelineController = MockRoomTimelineController(timelineKind: .pinned)
|
||||||
timelineController.timelineItems = []
|
timelineController.timelineItems = []
|
||||||
return TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Preview room")),
|
return TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Preview room")),
|
||||||
timelineController: timelineController,
|
timelineController: timelineController,
|
||||||
isPinnedEventsTimeline: true,
|
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
|||||||
timelineViewModel = TimelineViewModel(roomProxy: parameters.roomProxy,
|
timelineViewModel = TimelineViewModel(roomProxy: parameters.roomProxy,
|
||||||
focussedEventID: parameters.focussedEvent?.eventID,
|
focussedEventID: parameters.focussedEvent?.eventID,
|
||||||
timelineController: parameters.timelineController,
|
timelineController: parameters.timelineController,
|
||||||
isPinnedEventsTimeline: false,
|
|
||||||
mediaProvider: parameters.mediaProvider,
|
mediaProvider: parameters.mediaProvider,
|
||||||
mediaPlayerProvider: parameters.mediaPlayerProvider,
|
mediaPlayerProvider: parameters.mediaPlayerProvider,
|
||||||
voiceMessageMediaManager: parameters.voiceMessageMediaManager,
|
voiceMessageMediaManager: parameters.voiceMessageMediaManager,
|
||||||
|
|||||||
+12
-11
@@ -54,19 +54,20 @@ struct PinnedItemsBannerView: View {
|
|||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var viewAllButton: some View {
|
private var viewAllButton: some View {
|
||||||
switch state {
|
Button { onViewAllButtonTap() } label: {
|
||||||
case .loaded:
|
Text(state.isLoading ? "" : L10n.screenRoomPinnedBannerViewAllButtonTitle)
|
||||||
Button { onViewAllButtonTap() } label: {
|
.font(.compound.bodyMDSemibold)
|
||||||
Text(L10n.screenRoomPinnedBannerViewAllButtonTitle)
|
.foregroundStyle(Color.compound.textPrimary)
|
||||||
.font(.compound.bodyMDSemibold)
|
.opacity(state.isLoading ? 0 : 1)
|
||||||
.foregroundStyle(Color.compound.textPrimary)
|
// Use overlay instead otherwise the sliding animation would not work
|
||||||
.padding(.horizontal, 16)
|
.overlay(alignment: .trailing) {
|
||||||
.padding(.vertical, 5)
|
ProgressView()
|
||||||
}
|
.opacity(state.isLoading ? 1 : 0)
|
||||||
case .loading:
|
}
|
||||||
ProgressView()
|
|
||||||
.padding(.horizontal, 16)
|
.padding(.horizontal, 16)
|
||||||
|
.padding(.vertical, 5)
|
||||||
}
|
}
|
||||||
|
.disabled(state.isLoading)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var content: some View {
|
private var content: some View {
|
||||||
|
|||||||
@@ -220,7 +220,6 @@ struct RoomScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
static let roomViewModel = RoomScreenViewModel.mock(roomProxyMock: roomProxyMock)
|
static let roomViewModel = RoomScreenViewModel.mock(roomProxyMock: roomProxyMock)
|
||||||
static let timelineViewModel = TimelineViewModel(roomProxy: roomProxyMock,
|
static let timelineViewModel = TimelineViewModel(roomProxy: roomProxyMock,
|
||||||
timelineController: MockRoomTimelineController(),
|
timelineController: MockRoomTimelineController(),
|
||||||
isPinnedEventsTimeline: false,
|
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
init(roomProxy: JoinedRoomProxyProtocol,
|
init(roomProxy: JoinedRoomProxyProtocol,
|
||||||
focussedEventID: String? = nil,
|
focussedEventID: String? = nil,
|
||||||
timelineController: RoomTimelineControllerProtocol,
|
timelineController: RoomTimelineControllerProtocol,
|
||||||
isPinnedEventsTimeline: Bool,
|
|
||||||
mediaProvider: MediaProviderProtocol,
|
mediaProvider: MediaProviderProtocol,
|
||||||
mediaPlayerProvider: MediaPlayerProviderProtocol,
|
mediaPlayerProvider: MediaPlayerProviderProtocol,
|
||||||
voiceMessageMediaManager: VoiceMessageMediaManagerProtocol,
|
voiceMessageMediaManager: VoiceMessageMediaManagerProtocol,
|
||||||
@@ -81,7 +80,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
appSettings: appSettings,
|
appSettings: appSettings,
|
||||||
analyticsService: analyticsService)
|
analyticsService: analyticsService)
|
||||||
|
|
||||||
super.init(initialViewState: TimelineViewState(isPinnedEventsTimeline: isPinnedEventsTimeline,
|
super.init(initialViewState: TimelineViewState(isPinnedEventsTimeline: timelineController.timelineKind == .pinned,
|
||||||
roomID: roomProxy.id,
|
roomID: roomProxy.id,
|
||||||
isEncryptedOneToOneRoom: roomProxy.isEncryptedOneToOneRoom,
|
isEncryptedOneToOneRoom: roomProxy.isEncryptedOneToOneRoom,
|
||||||
timelineViewState: TimelineState(focussedEvent: focussedEventID.map { .init(eventID: $0, appearance: .immediate) }),
|
timelineViewState: TimelineState(focussedEvent: focussedEventID.map { .init(eventID: $0, appearance: .immediate) }),
|
||||||
@@ -827,7 +826,6 @@ extension TimelineViewModel {
|
|||||||
static let mock = TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Preview room")),
|
static let mock = TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Preview room")),
|
||||||
focussedEventID: nil,
|
focussedEventID: nil,
|
||||||
timelineController: MockRoomTimelineController(),
|
timelineController: MockRoomTimelineController(),
|
||||||
isPinnedEventsTimeline: false,
|
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||||
@@ -838,8 +836,7 @@ extension TimelineViewModel {
|
|||||||
|
|
||||||
static let pinnedEventsTimelineMock = TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Preview room")),
|
static let pinnedEventsTimelineMock = TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Preview room")),
|
||||||
focussedEventID: nil,
|
focussedEventID: nil,
|
||||||
timelineController: MockRoomTimelineController(),
|
timelineController: MockRoomTimelineController(timelineKind: .pinned),
|
||||||
isPinnedEventsTimeline: true,
|
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ struct ReadReceiptsSummaryView_Previews: PreviewProvider, TestablePreview {
|
|||||||
let roomProxyMock = JoinedRoomProxyMock(.init(name: "Room", members: members))
|
let roomProxyMock = JoinedRoomProxyMock(.init(name: "Room", members: members))
|
||||||
let mock = TimelineViewModel(roomProxy: roomProxyMock,
|
let mock = TimelineViewModel(roomProxy: roomProxyMock,
|
||||||
timelineController: MockRoomTimelineController(),
|
timelineController: MockRoomTimelineController(),
|
||||||
isPinnedEventsTimeline: false,
|
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||||
|
|||||||
@@ -92,7 +92,6 @@ struct TimelineReadReceiptsView_Previews: PreviewProvider, TestablePreview {
|
|||||||
|
|
||||||
static let viewModel = TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Test", members: members)),
|
static let viewModel = TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Test", members: members)),
|
||||||
timelineController: MockRoomTimelineController(),
|
timelineController: MockRoomTimelineController(),
|
||||||
isPinnedEventsTimeline: false,
|
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||||
|
|||||||
-1
@@ -99,7 +99,6 @@ struct HighlightedTimelineItemTimeline_Previews: PreviewProvider {
|
|||||||
static let timelineViewModel = TimelineViewModel(roomProxy: roomProxyMock,
|
static let timelineViewModel = TimelineViewModel(roomProxy: roomProxyMock,
|
||||||
focussedEventID: focussedEventID,
|
focussedEventID: focussedEventID,
|
||||||
timelineController: MockRoomTimelineController(),
|
timelineController: MockRoomTimelineController(),
|
||||||
isPinnedEventsTimeline: false,
|
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||||
|
|||||||
@@ -84,7 +84,6 @@ struct TimelineView_Previews: PreviewProvider, TestablePreview {
|
|||||||
static let roomViewModel = RoomScreenViewModel.mock(roomProxyMock: roomProxyMock)
|
static let roomViewModel = RoomScreenViewModel.mock(roomProxyMock: roomProxyMock)
|
||||||
static let timelineViewModel = TimelineViewModel(roomProxy: roomProxyMock,
|
static let timelineViewModel = TimelineViewModel(roomProxy: roomProxyMock,
|
||||||
timelineController: MockRoomTimelineController(),
|
timelineController: MockRoomTimelineController(),
|
||||||
isPinnedEventsTimeline: false,
|
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let timeline = try await TimelineProxy(timeline: room.pinnedEventsTimeline(internalIdPrefix: nil, maxEventsToLoad: 100), isLive: true)
|
let timeline = try await TimelineProxy(timeline: room.pinnedEventsTimeline(internalIdPrefix: nil, maxEventsToLoad: 100), kind: .pinned)
|
||||||
await timeline.subscribeForUpdates()
|
await timeline.subscribeForUpdates()
|
||||||
innerPinnedEventsTimeline = timeline
|
innerPinnedEventsTimeline = timeline
|
||||||
return timeline
|
return timeline
|
||||||
@@ -172,7 +172,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
|
|||||||
self.roomListItem = roomListItem
|
self.roomListItem = roomListItem
|
||||||
self.room = room
|
self.room = room
|
||||||
|
|
||||||
timeline = try await TimelineProxy(timeline: room.timeline(), isLive: true)
|
timeline = try await TimelineProxy(timeline: room.timeline(), kind: .live)
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
await updateMembers()
|
await updateMembers()
|
||||||
@@ -216,7 +216,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
|
|||||||
func timelineFocusedOnEvent(eventID: String, numberOfEvents: UInt16) async -> Result<TimelineProxyProtocol, RoomProxyError> {
|
func timelineFocusedOnEvent(eventID: String, numberOfEvents: UInt16) async -> Result<TimelineProxyProtocol, RoomProxyError> {
|
||||||
do {
|
do {
|
||||||
let timeline = try await room.timelineFocusedOnEvent(eventId: eventID, numContextEvents: numberOfEvents, internalIdPrefix: UUID().uuidString)
|
let timeline = try await room.timelineFocusedOnEvent(eventId: eventID, numContextEvents: numberOfEvents, internalIdPrefix: UUID().uuidString)
|
||||||
return .success(TimelineProxy(timeline: timeline, isLive: false))
|
return .success(TimelineProxy(timeline: timeline, kind: .detached))
|
||||||
} catch let error as FocusEventError {
|
} catch let error as FocusEventError {
|
||||||
switch error {
|
switch error {
|
||||||
case .InvalidEventId(_, let error):
|
case .InvalidEventId(_, let error):
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
|
|||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
private(set) var isLive: Bool
|
let kind: TimelineKind
|
||||||
|
|
||||||
private let membershipChangeSubject = PassthroughSubject<Void, Never>()
|
private let membershipChangeSubject = PassthroughSubject<Void, Never>()
|
||||||
var membershipChangePublisher: AnyPublisher<Void, Never> {
|
var membershipChangePublisher: AnyPublisher<Void, Never> {
|
||||||
@@ -54,10 +54,10 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
|
|||||||
roomTimelineObservationToken?.cancel()
|
roomTimelineObservationToken?.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(timeline: Timeline, isLive: Bool, paginationStatePublisher: AnyPublisher<PaginationState, Never>) {
|
init(timeline: Timeline, kind: TimelineKind, paginationStatePublisher: AnyPublisher<PaginationState, Never>) {
|
||||||
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomtimelineprovider", qos: .utility)
|
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomtimelineprovider", qos: .utility)
|
||||||
itemProxiesSubject = CurrentValueSubject<[TimelineItemProxy], Never>([])
|
itemProxiesSubject = CurrentValueSubject<[TimelineItemProxy], Never>([])
|
||||||
self.isLive = isLive
|
self.kind = kind
|
||||||
|
|
||||||
paginationStatePublisher
|
paginationStatePublisher
|
||||||
.sink { [weak self] in
|
.sink { [weak self] in
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ protocol RoomTimelineProviderProtocol {
|
|||||||
var itemProxies: [TimelineItemProxy] { get }
|
var itemProxies: [TimelineItemProxy] { get }
|
||||||
/// Whether the timeline is back/forward paginating or not (or has reached the start/end of the room).
|
/// Whether the timeline is back/forward paginating or not (or has reached the start/end of the room).
|
||||||
var paginationState: PaginationState { get }
|
var paginationState: PaginationState { get }
|
||||||
/// Whether or not the provider is for a live timeline.
|
/// The kind of the timeline
|
||||||
var isLive: Bool { get }
|
var kind: TimelineKind { get }
|
||||||
/// A publisher that signals when changes to the room's membership have occurred through `/sync`.
|
/// A publisher that signals when changes to the room's membership have occurred through `/sync`.
|
||||||
///
|
///
|
||||||
/// This is temporary and will be replace by a subscription on the room itself.
|
/// This is temporary and will be replace by a subscription on the room itself.
|
||||||
|
|||||||
+3
-1
@@ -28,6 +28,7 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
|
|
||||||
var roomProxy: JoinedRoomProxyProtocol?
|
var roomProxy: JoinedRoomProxyProtocol?
|
||||||
var roomID: String { roomProxy?.id ?? "MockRoomIdentifier" }
|
var roomID: String { roomProxy?.id ?? "MockRoomIdentifier" }
|
||||||
|
var timelineKind: TimelineKind
|
||||||
|
|
||||||
let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()
|
let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()
|
||||||
|
|
||||||
@@ -36,7 +37,8 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
|
|
||||||
private var client: UITestsSignalling.Client?
|
private var client: UITestsSignalling.Client?
|
||||||
|
|
||||||
init(listenForSignals: Bool = false) {
|
init(timelineKind: TimelineKind = .live, listenForSignals: Bool = false) {
|
||||||
|
self.timelineKind = timelineKind
|
||||||
callbacks.send(.paginationState(PaginationState(backward: .idle, forward: .timelineEndReached)))
|
callbacks.send(.paginationState(PaginationState(backward: .idle, forward: .timelineEndReached)))
|
||||||
callbacks.send(.isLive(true))
|
callbacks.send(.isLive(true))
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
private let timelineItemFactory: RoomTimelineItemFactoryProtocol
|
private let timelineItemFactory: RoomTimelineItemFactoryProtocol
|
||||||
private let appSettings: AppSettings
|
private let appSettings: AppSettings
|
||||||
private let serialDispatchQueue: DispatchQueue
|
private let serialDispatchQueue: DispatchQueue
|
||||||
private let shouldHideStart: Bool
|
|
||||||
|
|
||||||
let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()
|
let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()
|
||||||
|
|
||||||
@@ -42,17 +41,19 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
roomProxy.id
|
roomProxy.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var timelineKind: TimelineKind {
|
||||||
|
liveTimelineProvider.kind
|
||||||
|
}
|
||||||
|
|
||||||
init(roomProxy: JoinedRoomProxyProtocol,
|
init(roomProxy: JoinedRoomProxyProtocol,
|
||||||
timelineProxy: TimelineProxyProtocol,
|
timelineProxy: TimelineProxyProtocol,
|
||||||
initialFocussedEventID: String?,
|
initialFocussedEventID: String?,
|
||||||
shouldHideStart: Bool,
|
|
||||||
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
||||||
appSettings: AppSettings) {
|
appSettings: AppSettings) {
|
||||||
self.roomProxy = roomProxy
|
self.roomProxy = roomProxy
|
||||||
liveTimelineProvider = timelineProxy.timelineProvider
|
liveTimelineProvider = timelineProxy.timelineProvider
|
||||||
self.timelineItemFactory = timelineItemFactory
|
self.timelineItemFactory = timelineItemFactory
|
||||||
self.appSettings = appSettings
|
self.appSettings = appSettings
|
||||||
self.shouldHideStart = shouldHideStart
|
|
||||||
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomtimelineprovider", qos: .utility)
|
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomtimelineprovider", qos: .utility)
|
||||||
|
|
||||||
activeTimeline = timelineProxy
|
activeTimeline = timelineProxy
|
||||||
@@ -312,7 +313,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
|
|
||||||
// Inform the world that the initial items are loading from the store
|
// Inform the world that the initial items are loading from the store
|
||||||
callbacks.send(.paginationState(.init(backward: .paginating, forward: .paginating)))
|
callbacks.send(.paginationState(.init(backward: .paginating, forward: .paginating)))
|
||||||
callbacks.send(.isLive(activeTimelineProvider.isLive))
|
callbacks.send(.isLive(activeTimelineProvider.kind == .live))
|
||||||
|
|
||||||
updateTimelineItemsCancellable = activeTimelineProvider
|
updateTimelineItemsCancellable = activeTimelineProvider
|
||||||
.updatePublisher
|
.updatePublisher
|
||||||
@@ -367,7 +368,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
// Check if we need to add anything to the top of the timeline.
|
// Check if we need to add anything to the top of the timeline.
|
||||||
switch paginationState.backward {
|
switch paginationState.backward {
|
||||||
case .timelineEndReached:
|
case .timelineEndReached:
|
||||||
if !shouldHideStart, !roomProxy.isEncryptedOneToOneRoom {
|
if timelineKind != .pinned, !roomProxy.isEncryptedOneToOneRoom {
|
||||||
let timelineStart = TimelineStartRoomTimelineItem(name: roomProxy.name)
|
let timelineStart = TimelineStartRoomTimelineItem(name: roomProxy.name)
|
||||||
newTimelineItems.insert(timelineStart, at: 0)
|
newTimelineItems.insert(timelineStart, at: 0)
|
||||||
}
|
}
|
||||||
|
|||||||
-2
@@ -23,7 +23,6 @@ struct RoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol {
|
|||||||
RoomTimelineController(roomProxy: roomProxy,
|
RoomTimelineController(roomProxy: roomProxy,
|
||||||
timelineProxy: roomProxy.timeline,
|
timelineProxy: roomProxy.timeline,
|
||||||
initialFocussedEventID: initialFocussedEventID,
|
initialFocussedEventID: initialFocussedEventID,
|
||||||
shouldHideStart: false,
|
|
||||||
timelineItemFactory: timelineItemFactory,
|
timelineItemFactory: timelineItemFactory,
|
||||||
appSettings: ServiceLocator.shared.settings)
|
appSettings: ServiceLocator.shared.settings)
|
||||||
}
|
}
|
||||||
@@ -36,7 +35,6 @@ struct RoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol {
|
|||||||
return RoomTimelineController(roomProxy: roomProxy,
|
return RoomTimelineController(roomProxy: roomProxy,
|
||||||
timelineProxy: pinnedEventsTimeline,
|
timelineProxy: pinnedEventsTimeline,
|
||||||
initialFocussedEventID: nil,
|
initialFocussedEventID: nil,
|
||||||
shouldHideStart: true,
|
|
||||||
timelineItemFactory: timelineItemFactory,
|
timelineItemFactory: timelineItemFactory,
|
||||||
appSettings: ServiceLocator.shared.settings)
|
appSettings: ServiceLocator.shared.settings)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -38,6 +38,7 @@ enum RoomTimelineControllerError: Error {
|
|||||||
@MainActor
|
@MainActor
|
||||||
protocol RoomTimelineControllerProtocol {
|
protocol RoomTimelineControllerProtocol {
|
||||||
var roomID: String { get }
|
var roomID: String { get }
|
||||||
|
var timelineKind: TimelineKind { get }
|
||||||
|
|
||||||
var timelineItems: [RoomTimelineItemProtocol] { get }
|
var timelineItems: [RoomTimelineItemProtocol] { get }
|
||||||
var callbacks: PassthroughSubject<RoomTimelineControllerCallback, Never> { get }
|
var callbacks: PassthroughSubject<RoomTimelineControllerCallback, Never> { get }
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
|||||||
private let backPaginationStatusSubject = CurrentValueSubject<PaginationStatus, Never>(.timelineEndReached)
|
private let backPaginationStatusSubject = CurrentValueSubject<PaginationStatus, Never>(.timelineEndReached)
|
||||||
private let forwardPaginationStatusSubject = CurrentValueSubject<PaginationStatus, Never>(.timelineEndReached)
|
private let forwardPaginationStatusSubject = CurrentValueSubject<PaginationStatus, Never>(.timelineEndReached)
|
||||||
|
|
||||||
let isLive: Bool
|
private let kind: TimelineKind
|
||||||
|
|
||||||
private var innerTimelineProvider: RoomTimelineProviderProtocol!
|
private var innerTimelineProvider: RoomTimelineProviderProtocol!
|
||||||
var timelineProvider: RoomTimelineProviderProtocol {
|
var timelineProvider: RoomTimelineProviderProtocol {
|
||||||
@@ -38,9 +38,9 @@ final class TimelineProxy: TimelineProxyProtocol {
|
|||||||
backPaginationStatusObservationToken?.cancel()
|
backPaginationStatusObservationToken?.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(timeline: Timeline, isLive: Bool) {
|
init(timeline: Timeline, kind: TimelineKind) {
|
||||||
self.timeline = timeline
|
self.timeline = timeline
|
||||||
self.isLive = isLive
|
self.kind = kind
|
||||||
}
|
}
|
||||||
|
|
||||||
func subscribeForUpdates() async {
|
func subscribeForUpdates() async {
|
||||||
@@ -56,7 +56,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
|||||||
|
|
||||||
await subscribeToPagination()
|
await subscribeToPagination()
|
||||||
|
|
||||||
let provider = await RoomTimelineProvider(timeline: timeline, isLive: isLive, paginationStatePublisher: paginationStatePublisher)
|
let provider = await RoomTimelineProvider(timeline: timeline, kind: kind, paginationStatePublisher: paginationStatePublisher)
|
||||||
// Make sure the existing items are built so that we have content in the timeline before
|
// 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.
|
// determining whether or not the timeline should paginate to load more items.
|
||||||
await provider.waitForInitialItems()
|
await provider.waitForInitialItems()
|
||||||
@@ -84,15 +84,21 @@ final class TimelineProxy: TimelineProxyProtocol {
|
|||||||
// We can't subscribe to back pagination on detached timelines and as live timelines
|
// We can't subscribe to back pagination on detached timelines and as live timelines
|
||||||
// can be shared between multiple instances of the same room on the stack, it is
|
// can be shared between multiple instances of the same room on the stack, it is
|
||||||
// safer to still use the subscription logic for back pagination when live.
|
// safer to still use the subscription logic for back pagination when live.
|
||||||
await if isLive {
|
switch kind {
|
||||||
paginateBackwardsOnLive(requestSize: requestSize)
|
case .live:
|
||||||
} else {
|
return await paginateBackwardsOnLive(requestSize: requestSize)
|
||||||
focussedPaginate(.backwards, requestSize: requestSize)
|
case .detached:
|
||||||
|
return await focussedPaginate(.backwards, requestSize: requestSize)
|
||||||
|
case .pinned:
|
||||||
|
return .success(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func paginateForwards(requestSize: UInt16) async -> Result<Void, TimelineProxyError> {
|
func paginateForwards(requestSize: UInt16) async -> Result<Void, TimelineProxyError> {
|
||||||
await focussedPaginate(.forwards, requestSize: requestSize)
|
guard kind != .pinned else {
|
||||||
|
return .success(())
|
||||||
|
}
|
||||||
|
return await focussedPaginate(.forwards, requestSize: requestSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Paginate backwards using the subscription from Rust to drive the pagination state.
|
/// Paginate backwards using the subscription from Rust to drive the pagination state.
|
||||||
@@ -538,7 +544,8 @@ final class TimelineProxy: TimelineProxyProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func subscribeToPagination() async {
|
private func subscribeToPagination() async {
|
||||||
if isLive {
|
switch kind {
|
||||||
|
case .live:
|
||||||
let backPaginationListener = RoomPaginationStatusListener { [weak self] status in
|
let backPaginationListener = RoomPaginationStatusListener { [weak self] status in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
@@ -557,13 +564,15 @@ final class TimelineProxy: TimelineProxyProtocol {
|
|||||||
} catch {
|
} catch {
|
||||||
MXLog.error("Failed to subscribe to back pagination status with error: \(error)")
|
MXLog.error("Failed to subscribe to back pagination status with error: \(error)")
|
||||||
}
|
}
|
||||||
} else {
|
forwardPaginationStatusSubject.send(.timelineEndReached)
|
||||||
|
case .detached:
|
||||||
// Detached timelines don't support observation, set the initial state ourself.
|
// Detached timelines don't support observation, set the initial state ourself.
|
||||||
backPaginationStatusSubject.send(.idle)
|
backPaginationStatusSubject.send(.idle)
|
||||||
|
forwardPaginationStatusSubject.send(.idle)
|
||||||
|
case .pinned:
|
||||||
|
backPaginationStatusSubject.send(.timelineEndReached)
|
||||||
|
forwardPaginationStatusSubject.send(.timelineEndReached)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detached timelines don't support observation, set the initial state ourself.
|
|
||||||
forwardPaginationStatusSubject.send(isLive ? .timelineEndReached : .idle)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ import Combine
|
|||||||
import Foundation
|
import Foundation
|
||||||
import MatrixRustSDK
|
import MatrixRustSDK
|
||||||
|
|
||||||
|
enum TimelineKind {
|
||||||
|
case live
|
||||||
|
case detached
|
||||||
|
case pinned
|
||||||
|
}
|
||||||
|
|
||||||
enum TimelineProxyError: Error {
|
enum TimelineProxyError: Error {
|
||||||
case sdkError(Error)
|
case sdkError(Error)
|
||||||
|
|
||||||
|
|||||||
@@ -649,7 +649,6 @@ class MockScreen: Identifiable {
|
|||||||
let timelineController = RoomTimelineController(roomProxy: roomProxy,
|
let timelineController = RoomTimelineController(roomProxy: roomProxy,
|
||||||
timelineProxy: roomProxy.timeline,
|
timelineProxy: roomProxy.timeline,
|
||||||
initialFocussedEventID: nil,
|
initialFocussedEventID: nil,
|
||||||
shouldHideStart: false,
|
|
||||||
timelineItemFactory: RoomTimelineItemFactory(userID: "@alice:matrix.org",
|
timelineItemFactory: RoomTimelineItemFactory(userID: "@alice:matrix.org",
|
||||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||||
stateEventStringBuilder: RoomStateEventStringBuilder(userID: "@alice:matrix.org")),
|
stateEventStringBuilder: RoomStateEventStringBuilder(userID: "@alice:matrix.org")),
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:919e3df86ce263d06f479c701b6981a66b0d6217e13cf3cff12445b9c6a24d77
|
oid sha256:6ada9407d2f6c9da3cedb750353ffcf6e4cc6493177b3e2796d66b8245982042
|
||||||
size 106938
|
size 107677
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ class PillContextTests: XCTestCase {
|
|||||||
proxyMock.membersPublisher = subject.asCurrentValuePublisher()
|
proxyMock.membersPublisher = subject.asCurrentValuePublisher()
|
||||||
let mock = TimelineViewModel(roomProxy: proxyMock,
|
let mock = TimelineViewModel(roomProxy: proxyMock,
|
||||||
timelineController: MockRoomTimelineController(),
|
timelineController: MockRoomTimelineController(),
|
||||||
isPinnedEventsTimeline: false,
|
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||||
@@ -57,7 +56,6 @@ class PillContextTests: XCTestCase {
|
|||||||
proxyMock.membersPublisher = subject.asCurrentValuePublisher()
|
proxyMock.membersPublisher = subject.asCurrentValuePublisher()
|
||||||
let mock = TimelineViewModel(roomProxy: proxyMock,
|
let mock = TimelineViewModel(roomProxy: proxyMock,
|
||||||
timelineController: MockRoomTimelineController(),
|
timelineController: MockRoomTimelineController(),
|
||||||
isPinnedEventsTimeline: false,
|
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||||
@@ -79,7 +77,6 @@ class PillContextTests: XCTestCase {
|
|||||||
mockController.roomProxy = proxyMock
|
mockController.roomProxy = proxyMock
|
||||||
let mock = TimelineViewModel(roomProxy: proxyMock,
|
let mock = TimelineViewModel(roomProxy: proxyMock,
|
||||||
timelineController: mockController,
|
timelineController: mockController,
|
||||||
isPinnedEventsTimeline: false,
|
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||||
|
|||||||
@@ -345,7 +345,6 @@ class TimelineViewModelTests: XCTestCase {
|
|||||||
|
|
||||||
let viewModel = TimelineViewModel(roomProxy: roomProxy,
|
let viewModel = TimelineViewModel(roomProxy: roomProxy,
|
||||||
timelineController: timelineController,
|
timelineController: timelineController,
|
||||||
isPinnedEventsTimeline: false,
|
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||||
@@ -370,7 +369,6 @@ class TimelineViewModelTests: XCTestCase {
|
|||||||
timelineController.timelineItems = [message]
|
timelineController.timelineItems = [message]
|
||||||
let viewModel = TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "", members: [RoomMemberProxyMock.mockAlice, RoomMemberProxyMock.mockCharlie])),
|
let viewModel = TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "", members: [RoomMemberProxyMock.mockAlice, RoomMemberProxyMock.mockCharlie])),
|
||||||
timelineController: timelineController,
|
timelineController: timelineController,
|
||||||
isPinnedEventsTimeline: false,
|
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||||
@@ -396,7 +394,6 @@ class TimelineViewModelTests: XCTestCase {
|
|||||||
roomProxyMock.underlyingActionsPublisher = actionsSubject.eraseToAnyPublisher()
|
roomProxyMock.underlyingActionsPublisher = actionsSubject.eraseToAnyPublisher()
|
||||||
let viewModel = TimelineViewModel(roomProxy: roomProxyMock,
|
let viewModel = TimelineViewModel(roomProxy: roomProxyMock,
|
||||||
timelineController: MockRoomTimelineController(),
|
timelineController: MockRoomTimelineController(),
|
||||||
isPinnedEventsTimeline: false,
|
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||||
@@ -425,7 +422,6 @@ class TimelineViewModelTests: XCTestCase {
|
|||||||
roomProxyMock.underlyingActionsPublisher = actionsSubject.eraseToAnyPublisher()
|
roomProxyMock.underlyingActionsPublisher = actionsSubject.eraseToAnyPublisher()
|
||||||
let viewModel = TimelineViewModel(roomProxy: roomProxyMock,
|
let viewModel = TimelineViewModel(roomProxy: roomProxyMock,
|
||||||
timelineController: MockRoomTimelineController(),
|
timelineController: MockRoomTimelineController(),
|
||||||
isPinnedEventsTimeline: false,
|
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||||
@@ -455,7 +451,6 @@ class TimelineViewModelTests: XCTestCase {
|
|||||||
TimelineViewModel(roomProxy: roomProxy ?? JoinedRoomProxyMock(.init(name: "")),
|
TimelineViewModel(roomProxy: roomProxy ?? JoinedRoomProxyMock(.init(name: "")),
|
||||||
focussedEventID: focussedEventID,
|
focussedEventID: focussedEventID,
|
||||||
timelineController: timelineController,
|
timelineController: timelineController,
|
||||||
isPinnedEventsTimeline: false,
|
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||||
|
|||||||
Reference in New Issue
Block a user