diff --git a/System/AppDelegate.swift b/System/AppDelegate.swift index 4332bcb..a49f2a3 100644 --- a/System/AppDelegate.swift +++ b/System/AppDelegate.swift @@ -1,5 +1,6 @@ // Copyright © 2020 Metabolist. All rights reserved. +import AVKit import Combine import UIKit @@ -27,6 +28,8 @@ extension AppDelegate: UIApplicationDelegate { didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { self.application = application + try? AVAudioSession.sharedInstance().setCategory(.ambient, mode: .default) + return true } diff --git a/View Controllers/TableViewController.swift b/View Controllers/TableViewController.swift index 3902826..a93790a 100644 --- a/View Controllers/TableViewController.swift +++ b/View Controllers/TableViewController.swift @@ -1,5 +1,6 @@ // Copyright © 2020 Metabolist. All rights reserved. +import AVKit import Combine import SafariServices import SwiftUI @@ -170,6 +171,16 @@ extension TableViewController { } } +extension TableViewController: AVPlayerViewControllerDelegate { + func playerViewController( + _ playerViewController: AVPlayerViewController, + willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) { + try? AVAudioSession.sharedInstance().setCategory(.ambient, mode: .default) + playerViewController.player?.isMuted = true + updateAutoplayViews() + } +} + private extension TableViewController { static let autoplayViews = [PlayerView](repeating: .init(), count: 4) static var visibleVideoURLs = Set() @@ -250,7 +261,7 @@ private extension TableViewController { break case let .share(url): share(url: url) - case let.navigation(navigation): + case let .navigation(navigation): switch navigation { case let .collection(collectionService): show(TableViewController( @@ -273,6 +284,35 @@ private extension TableViewController { case .webfingerEnd: webfingerIndicatorView.stopAnimating() } + case let .attachment(attachmentViewModel, statusViewModel): + present(attachmentViewModel: attachmentViewModel, statusViewModel: statusViewModel) + } + } + + func present(attachmentViewModel: AttachmentViewModel, statusViewModel: StatusViewModel) { + switch attachmentViewModel.attachment.type { + case .audio, .video: + let playerViewController = AVPlayerViewController() + let player: AVPlayer + + if attachmentViewModel.attachment.type == .video { + player = PlayerCache.shared.player(url: attachmentViewModel.attachment.url) + } else { + player = AVPlayer(url: attachmentViewModel.attachment.url) + } + + playerViewController.delegate = self + playerViewController.player = player + + present(playerViewController, animated: true) { + try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default) + player.isMuted = false + player.play() + } + case .image, .gifv: + break + case .unknown: + break } } diff --git a/ViewModels/Sources/ViewModels/Entities/CollectionItemEvent.swift b/ViewModels/Sources/ViewModels/Entities/CollectionItemEvent.swift index 4577598..6e44485 100644 --- a/ViewModels/Sources/ViewModels/Entities/CollectionItemEvent.swift +++ b/ViewModels/Sources/ViewModels/Entities/CollectionItemEvent.swift @@ -6,5 +6,6 @@ import ServiceLayer public enum CollectionItemEvent { case ignorableOutput case navigation(Navigation) + case attachment(AttachmentViewModel, StatusViewModel) case share(URL) } diff --git a/ViewModels/Sources/ViewModels/StatusViewModel.swift b/ViewModels/Sources/ViewModels/StatusViewModel.swift index 25ddd3b..a90710d 100644 --- a/ViewModels/Sources/ViewModels/StatusViewModel.swift +++ b/ViewModels/Sources/ViewModels/StatusViewModel.swift @@ -127,28 +127,28 @@ public extension StatusViewModel { func toggleShowContent() { eventsSubject.send( statusService.toggleShowContent() - .map { _ in CollectionItemEvent.ignorableOutput } + .map { _ in .ignorableOutput } .eraseToAnyPublisher()) } func toggleShowAttachments() { eventsSubject.send( statusService.toggleShowAttachments() - .map { _ in CollectionItemEvent.ignorableOutput } + .map { _ in .ignorableOutput } .eraseToAnyPublisher()) } func urlSelected(_ url: URL) { eventsSubject.send( statusService.navigationService.item(url: url) - .map { CollectionItemEvent.navigation($0) } + .map { .navigation($0) } .setFailureType(to: Error.self) .eraseToAnyPublisher()) } func accountSelected() { eventsSubject.send( - Just(CollectionItemEvent.navigation( + Just(.navigation( .profile( statusService.navigationService.profileService( account: statusService.status.displayStatus.account)))) @@ -158,14 +158,14 @@ public extension StatusViewModel { func rebloggedBySelected() { eventsSubject.send( - Just(CollectionItemEvent.navigation(.collection(statusService.rebloggedByService()))) + Just(.navigation(.collection(statusService.rebloggedByService()))) .setFailureType(to: Error.self) .eraseToAnyPublisher()) } func favoritedBySelected() { eventsSubject.send( - Just(CollectionItemEvent.navigation(.collection(statusService.favoritedByService()))) + Just(.navigation(.collection(statusService.favoritedByService()))) .setFailureType(to: Error.self) .eraseToAnyPublisher()) } @@ -173,14 +173,18 @@ public extension StatusViewModel { func toggleFavorited() { eventsSubject.send( statusService.toggleFavorited() - .map { _ in CollectionItemEvent.ignorableOutput } + .map { _ in .ignorableOutput } .eraseToAnyPublisher()) } + func attachmentSelected(viewModel: AttachmentViewModel) { + eventsSubject.send(Just(.attachment(viewModel, self)).setFailureType(to: Error.self).eraseToAnyPublisher()) + } + func shareStatus() { guard let url = statusService.status.displayStatus.url else { return } - eventsSubject.send(Just(CollectionItemEvent.share(url)).setFailureType(to: Error.self).eraseToAnyPublisher()) + eventsSubject.send(Just(.share(url)).setFailureType(to: Error.self).eraseToAnyPublisher()) } } diff --git a/Views/Status/StatusAttachmentView.swift b/Views/Status/StatusAttachmentView.swift index 60af3da..8911067 100644 --- a/Views/Status/StatusAttachmentView.swift +++ b/Views/Status/StatusAttachmentView.swift @@ -109,6 +109,10 @@ private extension StatusAttachmentView { switch viewModel.attachment.type { case .image, .video, .gifv: imageView.kf.setImage(with: viewModel.attachment.previewUrl) + case .audio: + playImageView.image = UIImage(systemName: "waveform.circle", + withConfiguration: UIImage.SymbolConfiguration(textStyle: .largeTitle)) + backgroundColor = .secondarySystemBackground default: break } diff --git a/Views/Status/StatusAttachmentsView.swift b/Views/Status/StatusAttachmentsView.swift index 7d5c3b3..b7d7215 100644 --- a/Views/Status/StatusAttachmentsView.swift +++ b/Views/Status/StatusAttachmentsView.swift @@ -30,8 +30,12 @@ final class StatusAttachmentsView: UIView { rightStackView.isHidden = attachmentCount == 1 - for (index, viewModel) in attachmentViewModels.enumerated() { - let attachmentView = StatusAttachmentView(viewModel: viewModel) + for (index, attachmentViewModel) in attachmentViewModels.enumerated() { + let attachmentView = StatusAttachmentView(viewModel: attachmentViewModel) + + attachmentView.button.addAction( + UIAction { [weak self] _ in self?.viewModel?.attachmentSelected(viewModel: attachmentViewModel) }, + for: .touchUpInside) if attachmentCount == 2 && index == 1 || attachmentCount == 3 && index != 0