Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

File messages on timeline #311

Merged
merged 19 commits into from
Nov 16, 2022
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 108 additions & 14 deletions ElementX.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

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 }
}
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)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//
// 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 MediaPlayerCoordinatorParameters {
let mediaURL: URL
}

enum MediaPlayerCoordinatorAction {
case cancel
}

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

// MARK: Private

private let parameters: MediaPlayerCoordinatorParameters
private let mediaPlayerHostingController: UIViewController
private var mediaPlayerViewModel: MediaPlayerViewModelProtocol

private var indicatorPresenter: UserIndicatorTypePresenterProtocol
private var activityIndicator: UserIndicator?

// MARK: Public

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

// MARK: - Setup

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

let viewModel = MediaPlayerViewModel(mediaURL: parameters.mediaURL)
let view = MediaPlayerScreen(context: viewModel.context)
mediaPlayerViewModel = viewModel
mediaPlayerHostingController = UIHostingController(rootView: view)

indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: mediaPlayerHostingController)
}

// MARK: - Public

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

func toPresentable() -> UIViewController {
mediaPlayerHostingController
}

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
}
}
Loading