Skip to content

Commit

Permalink
Media gallery - part 1(#3588)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
stefanceriu authored Dec 6, 2024
1 parent a9e4837 commit caaa89a
Show file tree
Hide file tree
Showing 83 changed files with 1,247 additions and 279 deletions.
1 change: 1 addition & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ opt_in_rules:
- private_action
- explicit_init
- shorthand_optional_binding
- trailing_closure

included:
- ElementX
Expand Down
40 changes: 40 additions & 0 deletions ElementX.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions ElementX/Sources/Application/AppCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1048,7 +1048,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
.actionsPublisher
.filter(\.isSyncUpdate)
.collect(.byTimeOrCount(DispatchQueue.main, .seconds(10), 10))
.sink(receiveValue: { [weak self] _ in
.sink { [weak self] _ in
guard let self else { return }
MXLog.info("Background app refresh finished")
backgroundRefreshSyncObserver?.cancel()
Expand All @@ -1059,6 +1059,6 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
MXLog.info("Marking Background app refresh task as complete.")
task.setTaskCompleted(success: true)
}
})
}
}
}
4 changes: 4 additions & 0 deletions ElementX/Sources/Application/AppSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ final class AppSettings {
case enableOnlySignedDeviceIsolationMode
case knockingEnabled
case createMediaCaptionsEnabled
case mediaBrowserEnabled
}

private static var suiteName: String = InfoPlistReader.main.appGroupIdentifier
Expand Down Expand Up @@ -288,6 +289,9 @@ final class AppSettings {
@UserPreference(key: UserDefaultsKeys.createMediaCaptionsEnabled, defaultValue: false, storageType: .userDefaults(store))
var createMediaCaptionsEnabled

@UserPreference(key: UserDefaultsKeys.mediaBrowserEnabled, defaultValue: false, storageType: .userDefaults(store))
var mediaBrowserEnabled

#endif

// MARK: - Shared
Expand Down
4 changes: 2 additions & 2 deletions ElementX/Sources/Application/Application.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ struct Application: App {
openURLInSystemBrowser($0)
}
}
.onContinueUserActivity("INStartVideoCallIntent", perform: { userActivity in
.onContinueUserActivity("INStartVideoCallIntent") { userActivity in
// `INStartVideoCallIntent` is to be replaced with `INStartCallIntent`
// but calls from Recents still send it ¯\_(ツ)_/¯
appCoordinator.handleUserActivity(userActivity)
})
}
.task {
appCoordinator.start()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//
// Copyright 2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//

import Combine
import Foundation

enum MediaEventsTimelineFlowCoordinatorAction {
case finished
}

class MediaEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
private let navigationStackCoordinator: NavigationStackCoordinator
private let userSession: UserSessionProtocol
private let roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol
private let roomProxy: JoinedRoomProxyProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private let appMediator: AppMediatorProtocol
private let emojiProvider: EmojiProviderProtocol

private let actionsSubject: PassthroughSubject<MediaEventsTimelineFlowCoordinatorAction, Never> = .init()
var actionsPublisher: AnyPublisher<MediaEventsTimelineFlowCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}

private var cancellables = Set<AnyCancellable>()

init(navigationStackCoordinator: NavigationStackCoordinator,
userSession: UserSessionProtocol,
roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol,
roomProxy: JoinedRoomProxyProtocol,
userIndicatorController: UserIndicatorControllerProtocol,
appMediator: AppMediatorProtocol,
emojiProvider: EmojiProviderProtocol) {
self.navigationStackCoordinator = navigationStackCoordinator
self.userSession = userSession
self.roomTimelineControllerFactory = roomTimelineControllerFactory
self.roomProxy = roomProxy
self.userIndicatorController = userIndicatorController
self.appMediator = appMediator
self.emojiProvider = emojiProvider
}

func start() {
Task { await presentMediaEventsTimeline() }
}

func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
fatalError()
}

func clearRoute(animated: Bool) {
fatalError()
}

// MARK: - Private

private func presentMediaEventsTimeline() async {
let timelineItemFactory = RoomTimelineItemFactory(userID: userSession.clientProxy.userID,
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 {
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 {
MXLog.error("Failed presenting media timeline")
return
}

let parameters = MediaEventsTimelineScreenCoordinatorParameters(roomProxy: roomProxy,
mediaTimelineController: mediaTimelineController,
filesTimelineController: filesTimelineController,
mediaProvider: userSession.mediaProvider,
mediaPlayerProvider: MediaPlayerProvider(),
voiceMessageMediaManager: userSession.voiceMessageMediaManager,
appMediator: appMediator,
emojiProvider: emojiProvider)

let coordinator = MediaEventsTimelineScreenCoordinator(parameters: parameters)

navigationStackCoordinator.push(coordinator) { [weak self] in
self?.actionsSubject.send(.finished)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ class PinnedEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID))

guard let timelineController = await roomTimelineControllerFactory.buildRoomPinnedTimelineController(roomProxy: roomProxy,
timelineItemFactory: timelineItemFactory,
mediaProvider: userSession.mediaProvider) else {
guard let timelineController = await roomTimelineControllerFactory.buildPinnedEventsRoomTimelineController(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")
}

Expand Down
Loading

0 comments on commit caaa89a

Please sign in to comment.