mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Media gallery - part 1(#3588)
* Introduce a `MediaEventsTimelineFlowCoordinator` * Update SDK API and architecture * Add a feature flag, add translations * Move the media events timeline presentation under the room flow coordinator state machine * Rename `TimelineViewState.timelineViewState` of type `TimelineState` to `timelineState` * Enabled SwiftLint's `trailing_closure` rule and fix the warnings.
This commit is contained in:
+19
-4
@@ -23,6 +23,12 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
||||
|
||||
let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()
|
||||
|
||||
var paginationState: PaginationState = .initial {
|
||||
didSet {
|
||||
callbacks.send(.paginationState(paginationState))
|
||||
}
|
||||
}
|
||||
|
||||
var timelineItems: [RoomTimelineItemProtocol] = RoomTimelineItemFixtures.default
|
||||
var timelineItemsTimestamp: [TimelineItemIdentifier: Date] = [:]
|
||||
|
||||
@@ -30,9 +36,18 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
||||
|
||||
init(timelineKind: TimelineKind = .live, listenForSignals: Bool = false) {
|
||||
self.timelineKind = timelineKind
|
||||
callbacks.send(.paginationState(PaginationState(backward: .idle, forward: .timelineEndReached)))
|
||||
paginationState = PaginationState(backward: .idle, forward: .timelineEndReached)
|
||||
callbacks.send(.isLive(true))
|
||||
|
||||
switch timelineKind {
|
||||
case .media:
|
||||
timelineItems = (0..<5).reduce([]) { partialResult, _ in
|
||||
partialResult + RoomTimelineItemFixtures.mediaChunk
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
guard listenForSignals else { return }
|
||||
|
||||
do {
|
||||
@@ -56,7 +71,7 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
||||
}
|
||||
|
||||
func paginateBackwards(requestSize: UInt16) async -> Result<Void, RoomTimelineControllerError> {
|
||||
callbacks.send(.paginationState(PaginationState(backward: .paginating, forward: .timelineEndReached)))
|
||||
paginationState = PaginationState(backward: .paginating, forward: .timelineEndReached)
|
||||
|
||||
if client == nil {
|
||||
try? await simulateBackPagination()
|
||||
@@ -170,8 +185,8 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
||||
/// Prepends the next chunk of items to the `timelineItems` array.
|
||||
private func simulateBackPagination() async throws {
|
||||
defer {
|
||||
callbacks.send(.paginationState(PaginationState(backward: backPaginationResponses.isEmpty ? .timelineEndReached : .idle,
|
||||
forward: .timelineEndReached)))
|
||||
paginationState = PaginationState(backward: backPaginationResponses.isEmpty ? .timelineEndReached : .idle,
|
||||
forward: .timelineEndReached)
|
||||
}
|
||||
|
||||
guard !backPaginationResponses.isEmpty else { return }
|
||||
|
||||
@@ -30,6 +30,12 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
}
|
||||
|
||||
private(set) var timelineItems = [RoomTimelineItemProtocol]()
|
||||
|
||||
private(set) var paginationState: PaginationState = .initial {
|
||||
didSet {
|
||||
callbacks.send(.paginationState(paginationState))
|
||||
}
|
||||
}
|
||||
|
||||
var roomID: String {
|
||||
roomProxy.id
|
||||
@@ -64,7 +70,8 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
}
|
||||
|
||||
Task {
|
||||
callbacks.send(.paginationState(PaginationState(backward: .paginating, forward: .paginating)))
|
||||
paginationState = PaginationState(backward: .paginating, forward: .paginating)
|
||||
|
||||
switch await focusOnEvent(initialFocussedEventID, timelineSize: 100) {
|
||||
case .success:
|
||||
break
|
||||
@@ -368,7 +375,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
isSwitchingTimelines = true
|
||||
|
||||
// Inform the world that the initial items are loading from the store
|
||||
callbacks.send(.paginationState(.init(backward: .paginating, forward: .paginating)))
|
||||
paginationState = PaginationState(backward: .paginating, forward: .paginating)
|
||||
callbacks.send(.isLive(activeTimelineProvider.kind == .live))
|
||||
|
||||
updateTimelineItemsCancellable = activeTimelineProvider
|
||||
@@ -446,7 +453,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
}
|
||||
|
||||
callbacks.send(.updatedTimelineItems(timelineItems: newTimelineItems, isSwitchingTimelines: isNewTimeline))
|
||||
callbacks.send(.paginationState(paginationState))
|
||||
self.paginationState = paginationState
|
||||
}
|
||||
|
||||
private func buildTimelineItem(for itemProxy: TimelineItemProxy) -> RoomTimelineItemProtocol? {
|
||||
|
||||
+22
-3
@@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
struct RoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol {
|
||||
func buildRoomTimelineController(roomProxy: JoinedRoomProxyProtocol,
|
||||
@@ -20,12 +21,13 @@ struct RoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol {
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
}
|
||||
|
||||
func buildRoomPinnedTimelineController(roomProxy: JoinedRoomProxyProtocol,
|
||||
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
||||
mediaProvider: MediaProviderProtocol) async -> RoomTimelineControllerProtocol? {
|
||||
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,
|
||||
@@ -33,4 +35,21 @@ struct RoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
-3
@@ -6,6 +6,11 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
enum RoomTimelineFactoryControllerError: Error {
|
||||
case roomProxyError(RoomProxyError)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
protocol RoomTimelineControllerFactoryProtocol {
|
||||
@@ -13,9 +18,15 @@ protocol RoomTimelineControllerFactoryProtocol {
|
||||
initialFocussedEventID: String?,
|
||||
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
||||
mediaProvider: MediaProviderProtocol) -> RoomTimelineControllerProtocol
|
||||
func buildRoomPinnedTimelineController(roomProxy: JoinedRoomProxyProtocol,
|
||||
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
||||
mediaProvider: MediaProviderProtocol) async -> 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
|
||||
|
||||
+5
@@ -31,7 +31,12 @@ protocol RoomTimelineControllerProtocol {
|
||||
var roomID: String { get }
|
||||
var timelineKind: TimelineKind { get }
|
||||
|
||||
/// The currently known items, use only for setting up the intial state.
|
||||
var timelineItems: [RoomTimelineItemProtocol] { get }
|
||||
|
||||
/// The current pagination state, use only for setting up the intial state
|
||||
var paginationState: PaginationState { get }
|
||||
|
||||
var callbacks: PassthroughSubject<RoomTimelineControllerCallback, Never> { get }
|
||||
|
||||
func processItemAppearance(_ itemID: TimelineItemIdentifier) async
|
||||
|
||||
@@ -42,7 +42,7 @@ extension AggregatedReaction {
|
||||
|
||||
/// Whether to highlight the reaction, indicating that the current user sent this reaction.
|
||||
var isHighlighted: Bool {
|
||||
senders.contains(where: { $0.id == accountOwnerID })
|
||||
senders.contains { $0.id == accountOwnerID }
|
||||
}
|
||||
|
||||
/// The key to be displayed on screen. See `maxDisplayChars`.
|
||||
|
||||
@@ -13,7 +13,7 @@ import MatrixRustSDK
|
||||
/// Its value is consistent only per timeline instance, it should **not** be used to identify an item across timeline instances.
|
||||
/// - eventOrTransactionID: Contains the 2 possible identifiers of an event, either it has a remote event id or
|
||||
/// a local transaction id, never both or none.
|
||||
enum TimelineItemIdentifier: Hashable {
|
||||
enum TimelineItemIdentifier: Hashable, Sendable {
|
||||
case event(uniqueID: TimelineUniqueId, eventOrTransactionID: EventOrTransactionId)
|
||||
case virtual(uniqueID: TimelineUniqueId)
|
||||
|
||||
|
||||
+4
-3
@@ -61,9 +61,10 @@ struct VoiceMessageRoomPlaybackView: View {
|
||||
if let url = playerState.fileURL {
|
||||
WaveformView(audioURL: url,
|
||||
configuration: .init(style: .striped(.init(color: .black, width: waveformLineWidth, spacing: waveformLinePadding)),
|
||||
verticalScalingFactor: 1.0),
|
||||
placeholder: { estimatedWaveformView })
|
||||
.progressMask(progress: playerState.progress)
|
||||
verticalScalingFactor: 1.0)) {
|
||||
estimatedWaveformView
|
||||
}
|
||||
.progressMask(progress: playerState.progress)
|
||||
} else {
|
||||
estimatedWaveformView
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
switch kind {
|
||||
case .live:
|
||||
return await paginateBackwardsOnLive(requestSize: requestSize)
|
||||
case .detached:
|
||||
case .detached, .media:
|
||||
return await focussedPaginate(.backwards, requestSize: requestSize)
|
||||
case .pinned:
|
||||
return .success(())
|
||||
@@ -319,7 +319,6 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
return .success(())
|
||||
}
|
||||
|
||||
// swiftlint:disable:next function_parameter_count
|
||||
func sendVideo(url: URL,
|
||||
thumbnailURL: URL,
|
||||
videoInfo: VideoInfo,
|
||||
@@ -580,7 +579,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
MXLog.error("Failed to subscribe to back pagination status with error: \(error)")
|
||||
}
|
||||
forwardPaginationStatusSubject.send(.timelineEndReached)
|
||||
case .detached:
|
||||
case .detached, .media:
|
||||
// Detached timelines don't support observation, set the initial state ourself.
|
||||
backPaginationStatusSubject.send(.idle)
|
||||
forwardPaginationStatusSubject.send(.idle)
|
||||
|
||||
@@ -13,6 +13,7 @@ enum TimelineKind {
|
||||
case live
|
||||
case detached
|
||||
case pinned
|
||||
case media
|
||||
}
|
||||
|
||||
enum TimelineProxyError: Error {
|
||||
|
||||
Reference in New Issue
Block a user