Skip to content

Commit

Permalink
File messages on timeline (#311)
Browse files Browse the repository at this point in the history
* Create media player screen

* Introduce `FileCache` to cache message attachments

* Add file loading functionality into the media provider

* Process tap action on timeline items

* Pass item taps to view model

* Navigate to media player on view model callback

* Commit project file

* Add changelog

* Add file messages into the timeline

* Create file preview screen

* Display files in the preview screen

* Commit project file

* Update Rust SDK to 1.0.19-alpha

* Add changelog

* Bump the RustSDK to `v1.0.20-alpha`

* Configure audio session on video playback

Co-authored-by: Stefan Ceriu <stefanc@matrix.org>
  • Loading branch information
ismailgulek and stefanceriu authored Nov 16, 2022
1 parent ce6b004 commit 287fa4c
Show file tree
Hide file tree
Showing 30 changed files with 1,019 additions and 61 deletions.
190 changes: 139 additions & 51 deletions ElementX.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/matrix-org/matrix-rust-components-swift",
"state" : {
"revision" : "c8494d858cfb60eb6167d76790b819c9dc15e822",
"version" : "1.0.18-alpha"
"revision" : "f4ff25f54c7e854fa1498f6dfd7bedca8c0ed260",
"version" : "1.0.20-alpha"
}
},
{
Expand Down Expand Up @@ -129,7 +129,7 @@
{
"identity" : "swiftui-introspect",
"kind" : "remoteSourceControl",
"location" : "https://github.com/siteline/SwiftUI-Introspect.git",
"location" : "https://github.com/siteline/SwiftUI-Introspect",
"state" : {
"revision" : "f2616860a41f9d9932da412a8978fec79c06fe24",
"version" : "0.1.4"
Expand Down
4 changes: 2 additions & 2 deletions ElementX/Sources/Application/AppCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,11 @@ class AppCoordinator: Coordinator {
// This exposes the full Rust side tracing subscriber filter for more flexibility.
// We can filter by level, crate and even file. See more details here:
// https://docs.rs/tracing-subscriber/0.2.7/tracing_subscriber/filter/struct.EnvFilter.html#examples
setupTracing(configuration: "warn,hyper=warn,sled=warn,matrix_sdk_sled=warn")
setupTracing(filter: "warn,hyper=warn,sled=warn,matrix_sdk_sled=warn")

loggerConfiguration.logLevel = .debug
#else
setupTracing(configuration: "info,hyper=warn,sled=warn,matrix_sdk_sled=warn")
setupTracing(filter: "info,hyper=warn,sled=warn,matrix_sdk_sled=warn")
loggerConfiguration.logLevel = .info
#endif

Expand Down
95 changes: 95 additions & 0 deletions ElementX/Sources/Screens/FilePreview/FilePreviewCoordinator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import SwiftUI

struct FilePreviewCoordinatorParameters {
let fileURL: URL
let title: String?
}

enum FilePreviewCoordinatorAction {
case cancel
}

final class FilePreviewCoordinator: Coordinator, Presentable {
// MARK: - Properties

// MARK: Private

private let parameters: FilePreviewCoordinatorParameters
private let filePreviewHostingController: UIViewController
private var filePreviewViewModel: FilePreviewViewModelProtocol

private var indicatorPresenter: UserIndicatorTypePresenterProtocol
private var activityIndicator: UserIndicator?

// MARK: Public

// Must be used only internally
var childCoordinators: [Coordinator] = []
var callback: ((FilePreviewCoordinatorAction) -> Void)?

// MARK: - Setup

init(parameters: FilePreviewCoordinatorParameters) {
self.parameters = parameters

let viewModel = FilePreviewViewModel(fileURL: parameters.fileURL, title: parameters.title)
let view = FilePreviewScreen(context: viewModel.context)
filePreviewViewModel = viewModel
filePreviewHostingController = UIHostingController(rootView: view)

indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: filePreviewHostingController)
}

// MARK: - Public

func start() {
MXLog.debug("Did start.")
filePreviewViewModel.callback = { [weak self] action in
guard let self else { return }
MXLog.debug("FilePreviewViewModel did complete with result: \(action).")
switch action {
case .cancel:
self.callback?(.cancel)
}
}
}

func toPresentable() -> UIViewController {
filePreviewHostingController
}

func stop() {
stopLoading()
}

// MARK: - Private

/// Show an activity indicator whilst loading.
/// - Parameters:
/// - label: The label to show on the indicator.
/// - isInteractionBlocking: Whether the indicator should block any user interaction.
private func startLoading(label: String = ElementL10n.loading, isInteractionBlocking: Bool = true) {
activityIndicator = indicatorPresenter.present(.loading(label: label, isInteractionBlocking: isInteractionBlocking))
}

/// Hide the currently displayed activity indicator.
private func stopLoading() {
activityIndicator = nil
}
}
36 changes: 36 additions & 0 deletions ElementX/Sources/Screens/FilePreview/FilePreviewModels.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation

// MARK: - Coordinator

// MARK: View model

enum FilePreviewViewModelAction {
case cancel
}

// MARK: View

struct FilePreviewViewState: BindableState {
let fileURL: URL
let title: String?
}

enum FilePreviewViewAction {
case cancel
}
44 changes: 44 additions & 0 deletions ElementX/Sources/Screens/FilePreview/FilePreviewViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import SwiftUI

typealias FilePreviewViewModelType = StateStoreViewModel<FilePreviewViewState, FilePreviewViewAction>

class FilePreviewViewModel: FilePreviewViewModelType, FilePreviewViewModelProtocol {
// MARK: - Properties

// MARK: Private

// MARK: Public

var callback: ((FilePreviewViewModelAction) -> Void)?

// MARK: - Setup

init(fileURL: URL, title: String? = nil) {
super.init(initialViewState: FilePreviewViewState(fileURL: fileURL, title: title))
}

// MARK: - Public

override func process(viewAction: FilePreviewViewAction) async {
switch viewAction {
case .cancel:
callback?(.cancel)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation

@MainActor
protocol FilePreviewViewModelProtocol {
var callback: ((FilePreviewViewModelAction) -> Void)? { get set }
var context: FilePreviewViewModelType.Context { get }
}
95 changes: 95 additions & 0 deletions ElementX/Sources/Screens/FilePreview/View/FilePreviewScreen.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import QuickLook
import SwiftUI
import UIKit

struct FilePreviewScreen: View {
// MARK: Private

@Environment(\.colorScheme) private var colorScheme

var counterColor: Color {
colorScheme == .light ? .element.secondaryContent : .element.tertiaryContent
}

// MARK: Public

@ObservedObject var context: FilePreviewViewModel.Context

// MARK: Views

var body: some View {
PreviewController(fileURL: context.viewState.fileURL, title: context.viewState.title)
}
}

private struct PreviewController: UIViewControllerRepresentable {
let fileURL: URL
let title: String?

func makeUIViewController(context: Context) -> UINavigationController {
let controller = QLPreviewController()
controller.dataSource = context.coordinator

return UINavigationController(rootViewController: controller)
}

func updateUIViewController(_ uiViewController: UINavigationController, context: Context) { }

func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}

class Coordinator: QLPreviewControllerDataSource {
let parent: PreviewController

init(parent: PreviewController) {
self.parent = parent
}

func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
1
}

func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
PreviewItem(previewItemURL: parent.fileURL, previewItemTitle: parent.title)
}
}
}

private class PreviewItem: NSObject, QLPreviewItem {
var previewItemURL: URL?
var previewItemTitle: String?

init(previewItemURL: URL?, previewItemTitle: String?) {
self.previewItemURL = previewItemURL
self.previewItemTitle = previewItemTitle
}
}

// MARK: - Previews

struct FilePreview_Previews: PreviewProvider {
static var previews: some View {
Group {
let upgradeViewModel = FilePreviewViewModel(fileURL: URL(staticString: "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"))
FilePreviewScreen(context: upgradeViewModel.context)
}
.tint(.element.accent)
}
}
Loading

0 comments on commit 287fa4c

Please sign in to comment.