forked from shadowfacts/Tusker
Move content VCs to GalleryVC package
This commit is contained in:
parent
01cf597b5d
commit
5c86feccb9
|
@ -14,11 +14,15 @@ let package = Package(
|
||||||
name: "GalleryVC",
|
name: "GalleryVC",
|
||||||
targets: ["GalleryVC"]),
|
targets: ["GalleryVC"]),
|
||||||
],
|
],
|
||||||
|
dependencies: [
|
||||||
|
.package(path: "../TuskerComponents"),
|
||||||
|
],
|
||||||
targets: [
|
targets: [
|
||||||
// Targets are the basic building blocks of a package, defining a module or a test suite.
|
// Targets are the basic building blocks of a package, defining a module or a test suite.
|
||||||
// Targets can depend on other targets in this package and products from dependencies.
|
// Targets can depend on other targets in this package and products from dependencies.
|
||||||
.target(
|
.target(
|
||||||
name: "GalleryVC",
|
name: "GalleryVC",
|
||||||
|
dependencies: ["TuskerComponents"],
|
||||||
swiftSettings: [
|
swiftSettings: [
|
||||||
.swiftLanguageMode(.v5)
|
.swiftLanguageMode(.v5)
|
||||||
]),
|
]),
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
//
|
//
|
||||||
// FallbackGalleryContentViewController.swift
|
// FallbackGalleryContentViewController.swift
|
||||||
// Tusker
|
// GalleryVC
|
||||||
//
|
//
|
||||||
// Created by Shadowfacts on 3/18/24.
|
// Created by Shadowfacts on 3/18/24.
|
||||||
// Copyright © 2024 Shadowfacts. All rights reserved.
|
// Copyright © 2024 Shadowfacts. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import GalleryVC
|
|
||||||
import QuickLook
|
import QuickLook
|
||||||
import Pachyderm
|
|
||||||
|
|
||||||
private class FallbackGalleryContentViewController: QLPreviewController {
|
private class FallbackGalleryContentViewController: QLPreviewController {
|
||||||
private let previewItem = GalleryPreviewItem()
|
private let previewItem = GalleryPreviewItem()
|
||||||
|
@ -52,39 +50,39 @@ extension FallbackGalleryContentViewController: QLPreviewControllerDataSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FallbackGalleryNavigationController: UINavigationController, GalleryContentViewController {
|
public class FallbackGalleryNavigationController: UINavigationController, GalleryContentViewController {
|
||||||
init(url: URL) {
|
public init(url: URL) {
|
||||||
super.init(nibName: nil, bundle: nil)
|
super.init(nibName: nil, bundle: nil)
|
||||||
self.viewControllers = [FallbackGalleryContentViewController(url: url)]
|
self.viewControllers = [FallbackGalleryContentViewController(url: url)]
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
public override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
container?.disableGalleryScrollAndZoom()
|
container?.disableGalleryScrollAndZoom()
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
public required init?(coder aDecoder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: GalleryContentViewController
|
// MARK: GalleryContentViewController
|
||||||
|
|
||||||
weak var container: (any GalleryVC.GalleryContentViewControllerContainer)?
|
public weak var container: (any GalleryContentViewControllerContainer)?
|
||||||
|
|
||||||
var contentSize: CGSize {
|
public var contentSize: CGSize {
|
||||||
.zero
|
.zero
|
||||||
}
|
}
|
||||||
|
|
||||||
var activityItemsForSharing: [Any] {
|
public var activityItemsForSharing: [Any] {
|
||||||
[]
|
[]
|
||||||
}
|
}
|
||||||
|
|
||||||
var caption: String? {
|
public var caption: String? {
|
||||||
nil
|
nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var canAnimateFromSourceView: Bool {
|
public var canAnimateFromSourceView: Bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,22 +1,22 @@
|
||||||
//
|
//
|
||||||
// ImageGalleryContentViewController.swift
|
// ImageGalleryContentViewController.swift
|
||||||
// Tusker
|
// GalleryVC
|
||||||
//
|
//
|
||||||
// Created by Shadowfacts on 3/17/24.
|
// Created by Shadowfacts on 3/17/24.
|
||||||
// Copyright © 2024 Shadowfacts. All rights reserved.
|
// Copyright © 2024 Shadowfacts. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import GalleryVC
|
|
||||||
import Pachyderm
|
|
||||||
import TuskerComponents
|
import TuskerComponents
|
||||||
@preconcurrency import VisionKit
|
@preconcurrency import VisionKit
|
||||||
|
|
||||||
class ImageGalleryContentViewController: UIViewController, GalleryContentViewController {
|
open class ImageGalleryContentViewController: UIViewController, GalleryContentViewController {
|
||||||
let url: URL
|
public let caption: String?
|
||||||
let caption: String?
|
public var image: UIImage {
|
||||||
let originalData: Data?
|
didSet {
|
||||||
let image: UIImage
|
imageView?.image = image
|
||||||
|
}
|
||||||
|
}
|
||||||
let gifController: GIFController?
|
let gifController: GIFController?
|
||||||
|
|
||||||
private var imageView: GIFImageView!
|
private var imageView: GIFImageView!
|
||||||
|
@ -27,12 +27,8 @@ class ImageGalleryContentViewController: UIViewController, GalleryContentViewCon
|
||||||
@available(iOS 16.0, macCatalyst 17.0, *)
|
@available(iOS 16.0, macCatalyst 17.0, *)
|
||||||
private var analysisInteraction: ImageAnalysisInteraction? { _analysisInteraction as? ImageAnalysisInteraction }
|
private var analysisInteraction: ImageAnalysisInteraction? { _analysisInteraction as? ImageAnalysisInteraction }
|
||||||
|
|
||||||
private var isGrayscale = false
|
public init(image: UIImage, caption: String?, gifController: GIFController?) {
|
||||||
|
|
||||||
init(url: URL, caption: String?, originalData: Data?, image: UIImage, gifController: GIFController?) {
|
|
||||||
self.url = url
|
|
||||||
self.caption = caption
|
self.caption = caption
|
||||||
self.originalData = originalData
|
|
||||||
self.image = image
|
self.image = image
|
||||||
self.gifController = gifController
|
self.gifController = gifController
|
||||||
|
|
||||||
|
@ -41,21 +37,14 @@ class ImageGalleryContentViewController: UIViewController, GalleryContentViewCon
|
||||||
preferredContentSize = image.size
|
preferredContentSize = image.size
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
public required init?(coder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
public override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
isGrayscale = Preferences.shared.grayscaleImages
|
imageView = GIFImageView(image: image)
|
||||||
let maybeGrayscaleImage = if isGrayscale {
|
|
||||||
ImageGrayscalifier.convert(url: url, image: image) ?? image
|
|
||||||
} else {
|
|
||||||
image
|
|
||||||
}
|
|
||||||
|
|
||||||
imageView = GIFImageView(image: maybeGrayscaleImage)
|
|
||||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
imageView.contentMode = .scaleAspectFill
|
imageView.contentMode = .scaleAspectFill
|
||||||
imageView.isUserInteractionEnabled = true
|
imageView.isUserInteractionEnabled = true
|
||||||
|
@ -86,11 +75,9 @@ class ImageGalleryContentViewController: UIViewController, GalleryContentViewCon
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
public override func viewWillAppear(_ animated: Bool) {
|
||||||
super.viewWillAppear(animated)
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
if let gifController {
|
if let gifController {
|
||||||
|
@ -98,37 +85,19 @@ class ImageGalleryContentViewController: UIViewController, GalleryContentViewCon
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func preferencesChanged() {
|
|
||||||
if isGrayscale != Preferences.shared.grayscaleImages {
|
|
||||||
isGrayscale = Preferences.shared.grayscaleImages
|
|
||||||
let image = if isGrayscale {
|
|
||||||
ImageGrayscalifier.convert(url: url, image: image)
|
|
||||||
} else {
|
|
||||||
image
|
|
||||||
}
|
|
||||||
if let image {
|
|
||||||
imageView.image = image
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: GalleryContentViewController
|
// MARK: GalleryContentViewController
|
||||||
|
|
||||||
weak var container: (any GalleryVC.GalleryContentViewControllerContainer)?
|
public weak var container: (any GalleryContentViewControllerContainer)?
|
||||||
|
|
||||||
var contentSize: CGSize {
|
public var contentSize: CGSize {
|
||||||
image.size
|
image.size
|
||||||
}
|
}
|
||||||
|
|
||||||
var activityItemsForSharing: [Any] {
|
open var activityItemsForSharing: [Any] {
|
||||||
if let data = originalData ?? image.pngData() {
|
return [image]
|
||||||
return [ImageActivityItemSource(data: data, url: url, image: image)]
|
|
||||||
} else {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setControlsVisible(_ visible: Bool, animated: Bool, dueToUserInteraction: Bool) {
|
public func setControlsVisible(_ visible: Bool, animated: Bool, dueToUserInteraction: Bool) {
|
||||||
if #available(iOS 16.0, macCatalyst 17.0, *),
|
if #available(iOS 16.0, macCatalyst 17.0, *),
|
||||||
let analysisInteraction {
|
let analysisInteraction {
|
||||||
analysisInteraction.setSupplementaryInterfaceHidden(!visible, animated: animated)
|
analysisInteraction.setSupplementaryInterfaceHidden(!visible, animated: animated)
|
||||||
|
@ -138,7 +107,7 @@ class ImageGalleryContentViewController: UIViewController, GalleryContentViewCon
|
||||||
|
|
||||||
@available(iOS 16.0, macCatalyst 17.0, *)
|
@available(iOS 16.0, macCatalyst 17.0, *)
|
||||||
extension ImageGalleryContentViewController: ImageAnalysisInteractionDelegate {
|
extension ImageGalleryContentViewController: ImageAnalysisInteractionDelegate {
|
||||||
func interaction(_ interaction: ImageAnalysisInteraction, shouldBeginAt point: CGPoint, for interactionType: ImageAnalysisInteraction.InteractionTypes) -> Bool {
|
public func interaction(_ interaction: ImageAnalysisInteraction, shouldBeginAt point: CGPoint, for interactionType: ImageAnalysisInteraction.InteractionTypes) -> Bool {
|
||||||
return container?.galleryControlsVisible ?? true
|
return container?.galleryControlsVisible ?? true
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,43 +7,42 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import GalleryVC
|
|
||||||
|
|
||||||
class LoadingGalleryContentViewController: UIViewController, GalleryContentViewController {
|
public class LoadingGalleryContentViewController: UIViewController, GalleryContentViewController {
|
||||||
private let fallbackCaption: String?
|
private let fallbackCaption: String?
|
||||||
private let provider: () async -> (any GalleryContentViewController)?
|
private let provider: () async -> (any GalleryContentViewController)?
|
||||||
private var wrapped: (any GalleryContentViewController)!
|
private var wrapped: (any GalleryContentViewController)!
|
||||||
|
|
||||||
weak var container: GalleryContentViewControllerContainer?
|
public weak var container: GalleryContentViewControllerContainer?
|
||||||
|
|
||||||
var contentSize: CGSize {
|
public var contentSize: CGSize {
|
||||||
wrapped?.contentSize ?? .zero
|
wrapped?.contentSize ?? .zero
|
||||||
}
|
}
|
||||||
|
|
||||||
var activityItemsForSharing: [Any] {
|
public var activityItemsForSharing: [Any] {
|
||||||
wrapped?.activityItemsForSharing ?? []
|
wrapped?.activityItemsForSharing ?? []
|
||||||
}
|
}
|
||||||
|
|
||||||
var caption: String? {
|
public var caption: String? {
|
||||||
wrapped?.caption ?? fallbackCaption
|
wrapped?.caption ?? fallbackCaption
|
||||||
}
|
}
|
||||||
|
|
||||||
var canAnimateFromSourceView: Bool {
|
public var canAnimateFromSourceView: Bool {
|
||||||
wrapped?.canAnimateFromSourceView ?? true
|
wrapped?.canAnimateFromSourceView ?? true
|
||||||
}
|
}
|
||||||
|
|
||||||
init(caption: String?, provider: @escaping () async -> (any GalleryContentViewController)?) {
|
public init(caption: String?, provider: @escaping () async -> (any GalleryContentViewController)?) {
|
||||||
self.fallbackCaption = caption
|
self.fallbackCaption = caption
|
||||||
self.provider = provider
|
self.provider = provider
|
||||||
|
|
||||||
super.init(nibName: nil, bundle: nil)
|
super.init(nibName: nil, bundle: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
public required init?(coder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
public override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
container?.setGalleryContentLoading(true)
|
container?.setGalleryContentLoading(true)
|
||||||
|
@ -81,7 +80,7 @@ class LoadingGalleryContentViewController: UIViewController, GalleryContentViewC
|
||||||
|
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.text = "Error Loading"
|
label.text = "Error Loading"
|
||||||
label.font = .preferredFont(forTextStyle: .title1).withTraits(.traitBold)!
|
label.font = UIFont(descriptor: .preferredFontDescriptor(withTextStyle: .title1).withSymbolicTraits(.traitBold)!, size: 0)
|
||||||
label.textColor = .secondaryLabel
|
label.textColor = .secondaryLabel
|
||||||
label.adjustsFontForContentSizeCategory = true
|
label.adjustsFontForContentSizeCategory = true
|
||||||
|
|
||||||
|
@ -102,15 +101,15 @@ class LoadingGalleryContentViewController: UIViewController, GalleryContentViewC
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
func setControlsVisible(_ visible: Bool, animated: Bool, dueToUserInteraction: Bool) {
|
public func setControlsVisible(_ visible: Bool, animated: Bool, dueToUserInteraction: Bool) {
|
||||||
wrapped?.setControlsVisible(visible, animated: animated, dueToUserInteraction: dueToUserInteraction)
|
wrapped?.setControlsVisible(visible, animated: animated, dueToUserInteraction: dueToUserInteraction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func galleryContentDidAppear() {
|
public func galleryContentDidAppear() {
|
||||||
wrapped?.galleryContentDidAppear()
|
wrapped?.galleryContentDidAppear()
|
||||||
}
|
}
|
||||||
|
|
||||||
func galleryContentWillDisappear() {
|
public func galleryContentWillDisappear() {
|
||||||
wrapped?.galleryContentWillDisappear()
|
wrapped?.galleryContentWillDisappear()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//
|
//
|
||||||
// VideoControlsViewController.swift
|
// VideoControlsViewController.swift
|
||||||
// Tusker
|
// GalleryVC
|
||||||
//
|
//
|
||||||
// Created by Shadowfacts on 3/21/24.
|
// Created by Shadowfacts on 3/21/24.
|
||||||
// Copyright © 2024 Shadowfacts. All rights reserved.
|
// Copyright © 2024 Shadowfacts. All rights reserved.
|
||||||
|
@ -19,27 +19,35 @@ class VideoControlsViewController: UIViewController {
|
||||||
|
|
||||||
private let player: AVPlayer
|
private let player: AVPlayer
|
||||||
|
|
||||||
private lazy var muteButton = MuteButton().configure {
|
private lazy var muteButton: MuteButton = {
|
||||||
$0.addTarget(self, action: #selector(muteButtonPressed), for: .touchUpInside)
|
let button = MuteButton()
|
||||||
$0.setMuted(false, animated: false)
|
button.addTarget(self, action: #selector(muteButtonPressed), for: .touchUpInside)
|
||||||
}
|
button.setMuted(false, animated: false)
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
private let timestampLabel = UILabel().configure {
|
private let timestampLabel: UILabel = {
|
||||||
$0.text = "0:00"
|
let label = UILabel()
|
||||||
$0.font = UIFontMetrics(forTextStyle: .caption1).scaledFont(for: .monospacedDigitSystemFont(ofSize: 13, weight: .regular))
|
label.text = "0:00"
|
||||||
}
|
label.font = UIFontMetrics(forTextStyle: .caption1).scaledFont(for: .monospacedDigitSystemFont(ofSize: 13, weight: .regular))
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
private lazy var scrubbingControl = VideoScrubbingControl().configure {
|
private lazy var scrubbingControl: VideoScrubbingControl = {
|
||||||
$0.heightAnchor.constraint(equalToConstant: 44).isActive = true
|
let control = VideoScrubbingControl()
|
||||||
$0.addTarget(self, action: #selector(scrubbingStarted), for: .editingDidBegin)
|
control.heightAnchor.constraint(equalToConstant: 44).isActive = true
|
||||||
$0.addTarget(self, action: #selector(scrubbingChanged), for: .editingChanged)
|
control.addTarget(self, action: #selector(scrubbingStarted), for: .editingDidBegin)
|
||||||
$0.addTarget(self, action: #selector(scrubbingEnded), for: .editingDidEnd)
|
control.addTarget(self, action: #selector(scrubbingChanged), for: .editingChanged)
|
||||||
}
|
control.addTarget(self, action: #selector(scrubbingEnded), for: .editingDidEnd)
|
||||||
|
return control
|
||||||
|
}()
|
||||||
|
|
||||||
private let timeRemainingLabel = UILabel().configure {
|
private let timeRemainingLabel: UILabel = {
|
||||||
$0.text = "-0:00"
|
let label = UILabel()
|
||||||
$0.font = UIFontMetrics(forTextStyle: .caption1).scaledFont(for: .monospacedDigitSystemFont(ofSize: 13, weight: .regular))
|
label.text = "-0:00"
|
||||||
}
|
label.font = UIFontMetrics(forTextStyle: .caption1).scaledFont(for: .monospacedDigitSystemFont(ofSize: 13, weight: .regular))
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
private lazy var optionsButton = MenuButton { [unowned self] in
|
private lazy var optionsButton = MenuButton { [unowned self] in
|
||||||
let imageName: String
|
let imageName: String
|
||||||
|
@ -70,17 +78,19 @@ class VideoControlsViewController: UIViewController {
|
||||||
return UIMenu(children: [speedMenu])
|
return UIMenu(children: [speedMenu])
|
||||||
}
|
}
|
||||||
|
|
||||||
private lazy var hStack = UIStackView(arrangedSubviews: [
|
private lazy var hStack: UIStackView = {
|
||||||
|
let stack = UIStackView(arrangedSubviews: [
|
||||||
muteButton,
|
muteButton,
|
||||||
timestampLabel,
|
timestampLabel,
|
||||||
scrubbingControl,
|
scrubbingControl,
|
||||||
timeRemainingLabel,
|
timeRemainingLabel,
|
||||||
optionsButton,
|
optionsButton,
|
||||||
]).configure {
|
])
|
||||||
$0.axis = .horizontal
|
stack.axis = .horizontal
|
||||||
$0.spacing = 8
|
stack.spacing = 8
|
||||||
$0.alignment = .center
|
stack.alignment = .center
|
||||||
}
|
return stack
|
||||||
|
}()
|
||||||
|
|
||||||
private var timestampObserverToken: Any?
|
private var timestampObserverToken: Any?
|
||||||
private var scrubberObserverToken: Any?
|
private var scrubberObserverToken: Any?
|
|
@ -1,68 +1,52 @@
|
||||||
//
|
//
|
||||||
// VideoGalleryContentViewController.swift
|
// VideoGalleryContentViewController.swift
|
||||||
// Tusker
|
// GalleryVC
|
||||||
//
|
//
|
||||||
// Created by Shadowfacts on 3/19/24.
|
// Created by Shadowfacts on 3/19/24.
|
||||||
// Copyright © 2024 Shadowfacts. All rights reserved.
|
// Copyright © 2024 Shadowfacts. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import GalleryVC
|
|
||||||
import AVFoundation
|
import AVFoundation
|
||||||
import CoreImage
|
import CoreImage
|
||||||
|
|
||||||
class VideoGalleryContentViewController: UIViewController, GalleryContentViewController {
|
open class VideoGalleryContentViewController: UIViewController, GalleryContentViewController {
|
||||||
private let url: URL
|
public let url: URL
|
||||||
let caption: String?
|
public let caption: String?
|
||||||
private var item: AVPlayerItem
|
public private(set) var item: AVPlayerItem
|
||||||
let player: AVPlayer
|
public let player: AVPlayer
|
||||||
|
|
||||||
private var isGrayscale: Bool
|
|
||||||
|
|
||||||
private var presentationSizeObservation: NSKeyValueObservation?
|
private var presentationSizeObservation: NSKeyValueObservation?
|
||||||
private var statusObservation: NSKeyValueObservation?
|
private var statusObservation: NSKeyValueObservation?
|
||||||
private var rateObservation: NSKeyValueObservation?
|
private var rateObservation: NSKeyValueObservation?
|
||||||
private var isFirstAppearance = true
|
|
||||||
private var hideControlsWorkItem: DispatchWorkItem?
|
private var hideControlsWorkItem: DispatchWorkItem?
|
||||||
private var audioSessionToken: AudioSessionCoordinator.Token?
|
|
||||||
|
|
||||||
init(url: URL, caption: String?) {
|
public init(url: URL, caption: String?) {
|
||||||
self.url = url
|
self.url = url
|
||||||
self.caption = caption
|
self.caption = caption
|
||||||
|
|
||||||
self.isGrayscale = Preferences.shared.grayscaleImages
|
|
||||||
|
|
||||||
let asset = AVAsset(url: url)
|
let asset = AVAsset(url: url)
|
||||||
self.item = VideoGalleryContentViewController.createItem(asset: asset)
|
self.item = Self.createItem(asset: asset)
|
||||||
self.player = AVPlayer(playerItem: item)
|
self.player = AVPlayer(playerItem: item)
|
||||||
|
|
||||||
super.init(nibName: nil, bundle: nil)
|
super.init(nibName: nil, bundle: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
public required init?(coder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func createItem(asset: AVAsset) -> AVPlayerItem {
|
open class func createItem(asset: AVAsset) -> AVPlayerItem {
|
||||||
let item = AVPlayerItem(asset: asset)
|
return AVPlayerItem(asset: asset)
|
||||||
if Preferences.shared.grayscaleImages {
|
|
||||||
#if os(visionOS)
|
|
||||||
#warning("Use async AVVideoComposition CIFilter initializer")
|
|
||||||
#else
|
|
||||||
let filter = CIFilter(name: "CIColorMonochrome")!
|
|
||||||
filter.setValue(CIColor(red: 0.85, green: 0.85, blue: 0.85), forKey: "inputColor")
|
|
||||||
filter.setValue(1.0, forKey: "inputIntensity")
|
|
||||||
|
|
||||||
item.videoComposition = AVVideoComposition(asset: asset, applyingCIFiltersWithHandler: { request in
|
|
||||||
filter.setValue(request.sourceImage, forKey: "inputImage")
|
|
||||||
request.finish(with: filter.outputImage!, context: nil)
|
|
||||||
})
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
return item
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
public func replaceCurrentItem(with item: AVPlayerItem) {
|
||||||
|
self.item = item
|
||||||
|
player.replaceCurrentItem(with: item)
|
||||||
|
updateItemObservations()
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
container?.setGalleryContentLoading(true)
|
container?.setGalleryContentLoading(true)
|
||||||
|
@ -87,19 +71,17 @@ class VideoGalleryContentViewController: UIViewController, GalleryContentViewCon
|
||||||
scheduleControlsHide()
|
scheduleControlsHide()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateItemObservations() {
|
private func updateItemObservations() {
|
||||||
presentationSizeObservation = item.observe(\.presentationSize, changeHandler: { [unowned self] item, _ in
|
presentationSizeObservation = item.observe(\.presentationSize, changeHandler: { [unowned self] item, _ in
|
||||||
MainActor.runUnsafely {
|
MainActor.assumeIsolated {
|
||||||
self.preferredContentSize = item.presentationSize
|
self.preferredContentSize = item.presentationSize
|
||||||
self.container?.galleryContentChanged()
|
self.container?.galleryContentChanged()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
statusObservation = item.observe(\.status, changeHandler: { [unowned self] item, _ in
|
statusObservation = item.observe(\.status, changeHandler: { [unowned self] item, _ in
|
||||||
MainActor.runUnsafely {
|
MainActor.assumeIsolated {
|
||||||
if item.status == .readyToPlay {
|
if item.status == .readyToPlay {
|
||||||
self.container?.setGalleryContentLoading(false)
|
self.container?.setGalleryContentLoading(false)
|
||||||
self.statusObservation = nil
|
self.statusObservation = nil
|
||||||
|
@ -120,7 +102,7 @@ class VideoGalleryContentViewController: UIViewController, GalleryContentViewCon
|
||||||
|
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.text = "Error Loading"
|
label.text = "Error Loading"
|
||||||
label.font = .preferredFont(forTextStyle: .title1).withTraits(.traitBold)!
|
label.font = UIFont(descriptor: .preferredFontDescriptor(withTextStyle: .title1).withSymbolicTraits(.traitBold)!, size: 0)
|
||||||
label.textColor = .secondaryLabel
|
label.textColor = .secondaryLabel
|
||||||
label.adjustsFontForContentSizeCategory = true
|
label.adjustsFontForContentSizeCategory = true
|
||||||
|
|
||||||
|
@ -148,22 +130,9 @@ class VideoGalleryContentViewController: UIViewController, GalleryContentViewCon
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func preferencesChanged() {
|
|
||||||
if isGrayscale != Preferences.shared.grayscaleImages {
|
|
||||||
let isPlaying = player.rate > 0
|
|
||||||
isGrayscale = Preferences.shared.grayscaleImages
|
|
||||||
item = VideoGalleryContentViewController.createItem(asset: item.asset)
|
|
||||||
player.replaceCurrentItem(with: item)
|
|
||||||
updateItemObservations()
|
|
||||||
if isPlaying {
|
|
||||||
player.play()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func scheduleControlsHide() {
|
private func scheduleControlsHide() {
|
||||||
hideControlsWorkItem = DispatchWorkItem { [weak self] in
|
hideControlsWorkItem = DispatchWorkItem { [weak self] in
|
||||||
MainActor.runUnsafely {
|
MainActor.assumeIsolated {
|
||||||
guard let self,
|
guard let self,
|
||||||
let container = self.container,
|
let container = self.container,
|
||||||
container.galleryControlsVisible else {
|
container.galleryControlsVisible else {
|
||||||
|
@ -177,24 +146,25 @@ class VideoGalleryContentViewController: UIViewController, GalleryContentViewCon
|
||||||
|
|
||||||
// MARK: GalleryContentViewController
|
// MARK: GalleryContentViewController
|
||||||
|
|
||||||
weak var container: (any GalleryVC.GalleryContentViewControllerContainer)?
|
public weak var container: (any GalleryVC.GalleryContentViewControllerContainer)?
|
||||||
|
|
||||||
var contentSize: CGSize {
|
public var contentSize: CGSize {
|
||||||
item.presentationSize
|
item.presentationSize
|
||||||
}
|
}
|
||||||
|
|
||||||
var activityItemsForSharing: [Any] {
|
open var activityItemsForSharing: [Any] {
|
||||||
[VideoActivityItemSource(asset: item.asset, url: url)]
|
// [VideoActivityItemSource(asset: item.asset, url: url)]
|
||||||
|
[]
|
||||||
}
|
}
|
||||||
|
|
||||||
private lazy var overlayVC = VideoOverlayViewController(player: player)
|
private lazy var overlayVC = VideoOverlayViewController(player: player)
|
||||||
var contentOverlayAccessoryViewController: UIViewController? {
|
public var contentOverlayAccessoryViewController: UIViewController? {
|
||||||
overlayVC
|
overlayVC
|
||||||
}
|
}
|
||||||
|
|
||||||
private(set) lazy var bottomControlsAccessoryViewController: UIViewController? = VideoControlsViewController(player: player)
|
public private(set) lazy var bottomControlsAccessoryViewController: UIViewController? = VideoControlsViewController(player: player)
|
||||||
|
|
||||||
func setControlsVisible(_ visible: Bool, animated: Bool, dueToUserInteraction: Bool) {
|
public func setControlsVisible(_ visible: Bool, animated: Bool, dueToUserInteraction: Bool) {
|
||||||
overlayVC.setVisible(visible)
|
overlayVC.setVisible(visible)
|
||||||
|
|
||||||
if !visible {
|
if !visible {
|
||||||
|
@ -205,25 +175,11 @@ class VideoGalleryContentViewController: UIViewController, GalleryContentViewCon
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func galleryContentDidAppear() {
|
open func galleryContentDidAppear() {
|
||||||
let wasFirstAppearance = isFirstAppearance
|
|
||||||
isFirstAppearance = false
|
|
||||||
|
|
||||||
audioSessionToken = AudioSessionCoordinator.shared.beginPlayback(mode: .video) {
|
|
||||||
if wasFirstAppearance {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.player.play()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func galleryContentWillDisappear() {
|
open func galleryContentWillDisappear() {
|
||||||
player.pause()
|
player.pause()
|
||||||
|
|
||||||
if let audioSessionToken {
|
|
||||||
AudioSessionCoordinator.shared.endPlayback(token: audioSessionToken)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -253,7 +209,7 @@ private class PlayerView: UIView {
|
||||||
playerLayer.videoGravity = .resizeAspect
|
playerLayer.videoGravity = .resizeAspect
|
||||||
|
|
||||||
presentationSizeObservation = item.observe(\.presentationSize, changeHandler: { [unowned self] _, _ in
|
presentationSizeObservation = item.observe(\.presentationSize, changeHandler: { [unowned self] _, _ in
|
||||||
MainActor.runUnsafely {
|
MainActor.assumeIsolated {
|
||||||
self.invalidateIntrinsicContentSize()
|
self.invalidateIntrinsicContentSize()
|
||||||
}
|
}
|
||||||
})
|
})
|
|
@ -1,6 +1,6 @@
|
||||||
//
|
//
|
||||||
// VideoOverlayViewController.swift
|
// VideoOverlayViewController.swift
|
||||||
// Tusker
|
// GalleryVC
|
||||||
//
|
//
|
||||||
// Created by Shadowfacts on 3/26/24.
|
// Created by Shadowfacts on 3/26/24.
|
||||||
// Copyright © 2024 Shadowfacts. All rights reserved.
|
// Copyright © 2024 Shadowfacts. All rights reserved.
|
||||||
|
@ -79,7 +79,7 @@ class VideoOverlayViewController: UIViewController {
|
||||||
])
|
])
|
||||||
|
|
||||||
rateObservation = player.observe(\.rate, changeHandler: { player, _ in
|
rateObservation = player.observe(\.rate, changeHandler: { player, _ in
|
||||||
MainActor.runUnsafely {
|
MainActor.assumeIsolated {
|
||||||
playPauseButton.image = player.rate > 0 ? VideoOverlayViewController.pauseImage : VideoOverlayViewController.playImage
|
playPauseButton.image = player.rate > 0 ? VideoOverlayViewController.pauseImage : VideoOverlayViewController.playImage
|
||||||
}
|
}
|
||||||
})
|
})
|
|
@ -203,20 +203,16 @@
|
||||||
D691771729A710520054D7EF /* ProfileNoContentCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691771629A710520054D7EF /* ProfileNoContentCollectionViewCell.swift */; };
|
D691771729A710520054D7EF /* ProfileNoContentCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691771629A710520054D7EF /* ProfileNoContentCollectionViewCell.swift */; };
|
||||||
D691771929A7B8820054D7EF /* ProfileHeaderMovedOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691771829A7B8820054D7EF /* ProfileHeaderMovedOverlayView.swift */; };
|
D691771929A7B8820054D7EF /* ProfileHeaderMovedOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691771829A7B8820054D7EF /* ProfileHeaderMovedOverlayView.swift */; };
|
||||||
D691772E29AA5D420054D7EF /* UserActivityHandlingContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691772D29AA5D420054D7EF /* UserActivityHandlingContext.swift */; };
|
D691772E29AA5D420054D7EF /* UserActivityHandlingContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691772D29AA5D420054D7EF /* UserActivityHandlingContext.swift */; };
|
||||||
D69261232BB3AEFB0023152C /* VideoOverlayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69261222BB3AEFB0023152C /* VideoOverlayViewController.swift */; };
|
|
||||||
D69261272BB3BA610023152C /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69261262BB3BA610023152C /* Box.swift */; };
|
D69261272BB3BA610023152C /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69261262BB3BA610023152C /* Box.swift */; };
|
||||||
D6934F2C2BA7AD32002B1C8D /* GalleryVC in Frameworks */ = {isa = PBXBuildFile; productRef = D6934F2B2BA7AD32002B1C8D /* GalleryVC */; };
|
D6934F2C2BA7AD32002B1C8D /* GalleryVC in Frameworks */ = {isa = PBXBuildFile; productRef = D6934F2B2BA7AD32002B1C8D /* GalleryVC */; };
|
||||||
D6934F2E2BA7AEF9002B1C8D /* StatusAttachmentsGalleryDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6934F2D2BA7AEF9002B1C8D /* StatusAttachmentsGalleryDataSource.swift */; };
|
D6934F2E2BA7AEF9002B1C8D /* StatusAttachmentsGalleryDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6934F2D2BA7AEF9002B1C8D /* StatusAttachmentsGalleryDataSource.swift */; };
|
||||||
D6934F302BA7AF91002B1C8D /* ImageGalleryContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6934F2F2BA7AF91002B1C8D /* ImageGalleryContentViewController.swift */; };
|
D6934F302BA7AF91002B1C8D /* GrayscalableImageGalleryContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6934F2F2BA7AF91002B1C8D /* GrayscalableImageGalleryContentViewController.swift */; };
|
||||||
D6934F322BA7E43E002B1C8D /* LoadingGalleryContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6934F312BA7E43E002B1C8D /* LoadingGalleryContentViewController.swift */; };
|
|
||||||
D6934F342BA8D65A002B1C8D /* ImageGalleryDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6934F332BA8D65A002B1C8D /* ImageGalleryDataSource.swift */; };
|
D6934F342BA8D65A002B1C8D /* ImageGalleryDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6934F332BA8D65A002B1C8D /* ImageGalleryDataSource.swift */; };
|
||||||
D6934F362BA8E020002B1C8D /* GifvGalleryContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6934F352BA8E020002B1C8D /* GifvGalleryContentViewController.swift */; };
|
D6934F362BA8E020002B1C8D /* GifvGalleryContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6934F352BA8E020002B1C8D /* GifvGalleryContentViewController.swift */; };
|
||||||
D6934F382BA8E2B7002B1C8D /* GifvController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6934F372BA8E2B7002B1C8D /* GifvController.swift */; };
|
D6934F382BA8E2B7002B1C8D /* GifvController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6934F372BA8E2B7002B1C8D /* GifvController.swift */; };
|
||||||
D6934F3A2BA8F3D7002B1C8D /* FallbackGalleryContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6934F392BA8F3D7002B1C8D /* FallbackGalleryContentViewController.swift */; };
|
D6934F3C2BAA0F80002B1C8D /* GrayscalableVideoGalleryContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6934F3B2BAA0F80002B1C8D /* GrayscalableVideoGalleryContentViewController.swift */; };
|
||||||
D6934F3C2BAA0F80002B1C8D /* VideoGalleryContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6934F3B2BAA0F80002B1C8D /* VideoGalleryContentViewController.swift */; };
|
|
||||||
D6934F3E2BAA19D5002B1C8D /* ImageActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6934F3D2BAA19D5002B1C8D /* ImageActivityItemSource.swift */; };
|
D6934F3E2BAA19D5002B1C8D /* ImageActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6934F3D2BAA19D5002B1C8D /* ImageActivityItemSource.swift */; };
|
||||||
D6934F402BAA19EC002B1C8D /* VideoActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6934F3F2BAA19EC002B1C8D /* VideoActivityItemSource.swift */; };
|
D6934F402BAA19EC002B1C8D /* VideoActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6934F3F2BAA19EC002B1C8D /* VideoActivityItemSource.swift */; };
|
||||||
D6934F422BAC7D6E002B1C8D /* VideoControlsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6934F412BAC7D6E002B1C8D /* VideoControlsViewController.swift */; };
|
|
||||||
D693A72825CF282E003A14E2 /* TrendingHashtagsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */; };
|
D693A72825CF282E003A14E2 /* TrendingHashtagsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */; };
|
||||||
D693DE5723FE1A6A0061E07D /* EnhancedNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693DE5623FE1A6A0061E07D /* EnhancedNavigationViewController.swift */; };
|
D693DE5723FE1A6A0061E07D /* EnhancedNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693DE5623FE1A6A0061E07D /* EnhancedNavigationViewController.swift */; };
|
||||||
D693DE5923FE24310061E07D /* InteractivePushTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693DE5823FE24300061E07D /* InteractivePushTransition.swift */; };
|
D693DE5923FE24310061E07D /* InteractivePushTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693DE5823FE24300061E07D /* InteractivePushTransition.swift */; };
|
||||||
|
@ -637,19 +633,15 @@
|
||||||
D691771629A710520054D7EF /* ProfileNoContentCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileNoContentCollectionViewCell.swift; sourceTree = "<group>"; };
|
D691771629A710520054D7EF /* ProfileNoContentCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileNoContentCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
D691771829A7B8820054D7EF /* ProfileHeaderMovedOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHeaderMovedOverlayView.swift; sourceTree = "<group>"; };
|
D691771829A7B8820054D7EF /* ProfileHeaderMovedOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHeaderMovedOverlayView.swift; sourceTree = "<group>"; };
|
||||||
D691772D29AA5D420054D7EF /* UserActivityHandlingContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivityHandlingContext.swift; sourceTree = "<group>"; };
|
D691772D29AA5D420054D7EF /* UserActivityHandlingContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivityHandlingContext.swift; sourceTree = "<group>"; };
|
||||||
D69261222BB3AEFB0023152C /* VideoOverlayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoOverlayViewController.swift; sourceTree = "<group>"; };
|
|
||||||
D69261262BB3BA610023152C /* Box.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Box.swift; sourceTree = "<group>"; };
|
D69261262BB3BA610023152C /* Box.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Box.swift; sourceTree = "<group>"; };
|
||||||
D6934F2D2BA7AEF9002B1C8D /* StatusAttachmentsGalleryDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusAttachmentsGalleryDataSource.swift; sourceTree = "<group>"; };
|
D6934F2D2BA7AEF9002B1C8D /* StatusAttachmentsGalleryDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusAttachmentsGalleryDataSource.swift; sourceTree = "<group>"; };
|
||||||
D6934F2F2BA7AF91002B1C8D /* ImageGalleryContentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageGalleryContentViewController.swift; sourceTree = "<group>"; };
|
D6934F2F2BA7AF91002B1C8D /* GrayscalableImageGalleryContentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrayscalableImageGalleryContentViewController.swift; sourceTree = "<group>"; };
|
||||||
D6934F312BA7E43E002B1C8D /* LoadingGalleryContentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingGalleryContentViewController.swift; sourceTree = "<group>"; };
|
|
||||||
D6934F332BA8D65A002B1C8D /* ImageGalleryDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageGalleryDataSource.swift; sourceTree = "<group>"; };
|
D6934F332BA8D65A002B1C8D /* ImageGalleryDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageGalleryDataSource.swift; sourceTree = "<group>"; };
|
||||||
D6934F352BA8E020002B1C8D /* GifvGalleryContentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GifvGalleryContentViewController.swift; sourceTree = "<group>"; };
|
D6934F352BA8E020002B1C8D /* GifvGalleryContentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GifvGalleryContentViewController.swift; sourceTree = "<group>"; };
|
||||||
D6934F372BA8E2B7002B1C8D /* GifvController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GifvController.swift; sourceTree = "<group>"; };
|
D6934F372BA8E2B7002B1C8D /* GifvController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GifvController.swift; sourceTree = "<group>"; };
|
||||||
D6934F392BA8F3D7002B1C8D /* FallbackGalleryContentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FallbackGalleryContentViewController.swift; sourceTree = "<group>"; };
|
D6934F3B2BAA0F80002B1C8D /* GrayscalableVideoGalleryContentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrayscalableVideoGalleryContentViewController.swift; sourceTree = "<group>"; };
|
||||||
D6934F3B2BAA0F80002B1C8D /* VideoGalleryContentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoGalleryContentViewController.swift; sourceTree = "<group>"; };
|
|
||||||
D6934F3D2BAA19D5002B1C8D /* ImageActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageActivityItemSource.swift; sourceTree = "<group>"; };
|
D6934F3D2BAA19D5002B1C8D /* ImageActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageActivityItemSource.swift; sourceTree = "<group>"; };
|
||||||
D6934F3F2BAA19EC002B1C8D /* VideoActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoActivityItemSource.swift; sourceTree = "<group>"; };
|
D6934F3F2BAA19EC002B1C8D /* VideoActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoActivityItemSource.swift; sourceTree = "<group>"; };
|
||||||
D6934F412BAC7D6E002B1C8D /* VideoControlsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoControlsViewController.swift; sourceTree = "<group>"; };
|
|
||||||
D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingHashtagsViewController.swift; sourceTree = "<group>"; };
|
D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingHashtagsViewController.swift; sourceTree = "<group>"; };
|
||||||
D693DE5623FE1A6A0061E07D /* EnhancedNavigationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnhancedNavigationViewController.swift; sourceTree = "<group>"; };
|
D693DE5623FE1A6A0061E07D /* EnhancedNavigationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnhancedNavigationViewController.swift; sourceTree = "<group>"; };
|
||||||
D693DE5823FE24300061E07D /* InteractivePushTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractivePushTransition.swift; sourceTree = "<group>"; };
|
D693DE5823FE24300061E07D /* InteractivePushTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractivePushTransition.swift; sourceTree = "<group>"; };
|
||||||
|
@ -899,13 +891,9 @@
|
||||||
children = (
|
children = (
|
||||||
D6934F2D2BA7AEF9002B1C8D /* StatusAttachmentsGalleryDataSource.swift */,
|
D6934F2D2BA7AEF9002B1C8D /* StatusAttachmentsGalleryDataSource.swift */,
|
||||||
D6934F332BA8D65A002B1C8D /* ImageGalleryDataSource.swift */,
|
D6934F332BA8D65A002B1C8D /* ImageGalleryDataSource.swift */,
|
||||||
D6934F312BA7E43E002B1C8D /* LoadingGalleryContentViewController.swift */,
|
D6934F2F2BA7AF91002B1C8D /* GrayscalableImageGalleryContentViewController.swift */,
|
||||||
D6934F2F2BA7AF91002B1C8D /* ImageGalleryContentViewController.swift */,
|
|
||||||
D6934F352BA8E020002B1C8D /* GifvGalleryContentViewController.swift */,
|
D6934F352BA8E020002B1C8D /* GifvGalleryContentViewController.swift */,
|
||||||
D6934F3B2BAA0F80002B1C8D /* VideoGalleryContentViewController.swift */,
|
D6934F3B2BAA0F80002B1C8D /* GrayscalableVideoGalleryContentViewController.swift */,
|
||||||
D69261222BB3AEFB0023152C /* VideoOverlayViewController.swift */,
|
|
||||||
D6934F412BAC7D6E002B1C8D /* VideoControlsViewController.swift */,
|
|
||||||
D6934F392BA8F3D7002B1C8D /* FallbackGalleryContentViewController.swift */,
|
|
||||||
);
|
);
|
||||||
path = Gallery;
|
path = Gallery;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -2122,7 +2110,6 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
D6934F422BAC7D6E002B1C8D /* VideoControlsViewController.swift in Sources */,
|
|
||||||
0427033A22B31269000D31B6 /* AdvancedPrefsView.swift in Sources */,
|
0427033A22B31269000D31B6 /* AdvancedPrefsView.swift in Sources */,
|
||||||
D6C3F4F5298ED0890009FCFF /* LocalPredicateStatusesViewController.swift in Sources */,
|
D6C3F4F5298ED0890009FCFF /* LocalPredicateStatusesViewController.swift in Sources */,
|
||||||
D691771729A710520054D7EF /* ProfileNoContentCollectionViewCell.swift in Sources */,
|
D691771729A710520054D7EF /* ProfileNoContentCollectionViewCell.swift in Sources */,
|
||||||
|
@ -2169,8 +2156,7 @@
|
||||||
D6A4DCCC2553667800D9DE31 /* FastAccountSwitcherViewController.swift in Sources */,
|
D6A4DCCC2553667800D9DE31 /* FastAccountSwitcherViewController.swift in Sources */,
|
||||||
D627944F23A9C99800D38C68 /* EditListAccountsViewController.swift in Sources */,
|
D627944F23A9C99800D38C68 /* EditListAccountsViewController.swift in Sources */,
|
||||||
D6945C3423AC6431005C403C /* AddSavedHashtagViewController.swift in Sources */,
|
D6945C3423AC6431005C403C /* AddSavedHashtagViewController.swift in Sources */,
|
||||||
D69261232BB3AEFB0023152C /* VideoOverlayViewController.swift in Sources */,
|
D6934F3C2BAA0F80002B1C8D /* GrayscalableVideoGalleryContentViewController.swift in Sources */,
|
||||||
D6934F3C2BAA0F80002B1C8D /* VideoGalleryContentViewController.swift in Sources */,
|
|
||||||
D6F6A552291F098700F496A8 /* RenameListService.swift in Sources */,
|
D6F6A552291F098700F496A8 /* RenameListService.swift in Sources */,
|
||||||
D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */,
|
D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */,
|
||||||
D698F46B2BD079F00054DB14 /* AnnouncementListRow.swift in Sources */,
|
D698F46B2BD079F00054DB14 /* AnnouncementListRow.swift in Sources */,
|
||||||
|
@ -2190,7 +2176,6 @@
|
||||||
D6D94955298963A900C59229 /* Colors.swift in Sources */,
|
D6D94955298963A900C59229 /* Colors.swift in Sources */,
|
||||||
D6945C3823AC739F005C403C /* InstanceTimelineViewController.swift in Sources */,
|
D6945C3823AC739F005C403C /* InstanceTimelineViewController.swift in Sources */,
|
||||||
D625E4822588262A0074BB2B /* DraggableTableViewCell.swift in Sources */,
|
D625E4822588262A0074BB2B /* DraggableTableViewCell.swift in Sources */,
|
||||||
D6934F322BA7E43E002B1C8D /* LoadingGalleryContentViewController.swift in Sources */,
|
|
||||||
D68E525B24A3D77E0054355A /* TuskerRootViewController.swift in Sources */,
|
D68E525B24A3D77E0054355A /* TuskerRootViewController.swift in Sources */,
|
||||||
D62D2422217AA7E1005076CC /* UserActivityManager.swift in Sources */,
|
D62D2422217AA7E1005076CC /* UserActivityManager.swift in Sources */,
|
||||||
D64A50482C739DEA009D7193 /* BaseMainTabBarViewController.swift in Sources */,
|
D64A50482C739DEA009D7193 /* BaseMainTabBarViewController.swift in Sources */,
|
||||||
|
@ -2218,7 +2203,6 @@
|
||||||
D630C3CC2BC5FD4600208903 /* GetAuthorizationTokenService.swift in Sources */,
|
D630C3CC2BC5FD4600208903 /* GetAuthorizationTokenService.swift in Sources */,
|
||||||
D61DC84B28F4FD2000B82C6E /* ProfileHeaderCollectionViewCell.swift in Sources */,
|
D61DC84B28F4FD2000B82C6E /* ProfileHeaderCollectionViewCell.swift in Sources */,
|
||||||
D61A45E828DF477D002BE511 /* LoadingCollectionViewCell.swift in Sources */,
|
D61A45E828DF477D002BE511 /* LoadingCollectionViewCell.swift in Sources */,
|
||||||
D6934F3A2BA8F3D7002B1C8D /* FallbackGalleryContentViewController.swift in Sources */,
|
|
||||||
D67B506D250B291200FAECFB /* BlurHashDecode.swift in Sources */,
|
D67B506D250B291200FAECFB /* BlurHashDecode.swift in Sources */,
|
||||||
D68A76EC295369A8001DA1B3 /* AboutView.swift in Sources */,
|
D68A76EC295369A8001DA1B3 /* AboutView.swift in Sources */,
|
||||||
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */,
|
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */,
|
||||||
|
@ -2341,7 +2325,7 @@
|
||||||
D61A45EA28DF51EE002BE511 /* TimelineLikeCollectionViewController.swift in Sources */,
|
D61A45EA28DF51EE002BE511 /* TimelineLikeCollectionViewController.swift in Sources */,
|
||||||
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */,
|
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */,
|
||||||
D64A50BE2C752247009D7193 /* AdaptableNavigationController.swift in Sources */,
|
D64A50BE2C752247009D7193 /* AdaptableNavigationController.swift in Sources */,
|
||||||
D6934F302BA7AF91002B1C8D /* ImageGalleryContentViewController.swift in Sources */,
|
D6934F302BA7AF91002B1C8D /* GrayscalableImageGalleryContentViewController.swift in Sources */,
|
||||||
D6934F3E2BAA19D5002B1C8D /* ImageActivityItemSource.swift in Sources */,
|
D6934F3E2BAA19D5002B1C8D /* ImageActivityItemSource.swift in Sources */,
|
||||||
D67C1795266D57D10070F250 /* FastAccountSwitcherIndicatorView.swift in Sources */,
|
D67C1795266D57D10070F250 /* FastAccountSwitcherIndicatorView.swift in Sources */,
|
||||||
D61F75962937037800C0B37F /* ToggleFollowHashtagService.swift in Sources */,
|
D61F75962937037800C0B37F /* ToggleFollowHashtagService.swift in Sources */,
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
//
|
||||||
|
// GrayscalableImageGalleryContentViewController.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 11/21/24.
|
||||||
|
// Copyright © 2024 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import TuskerComponents
|
||||||
|
import GalleryVC
|
||||||
|
|
||||||
|
class GrayscalableImageGalleryContentViewController: GalleryVC.ImageGalleryContentViewController {
|
||||||
|
private let url: URL
|
||||||
|
private let originalImage: UIImage
|
||||||
|
private let originalData: Data?
|
||||||
|
private var isGrayscale = false
|
||||||
|
|
||||||
|
init(url: URL, caption: String?, originalData: Data?, image: UIImage, gifController: GIFController?) {
|
||||||
|
self.url = url
|
||||||
|
self.originalImage = image
|
||||||
|
self.originalData = originalData
|
||||||
|
|
||||||
|
super.init(image: image, caption: caption, gifController: gifController)
|
||||||
|
|
||||||
|
isGrayscale = Preferences.shared.grayscaleImages
|
||||||
|
if isGrayscale {
|
||||||
|
self.image = ImageGrayscalifier.convert(url: url, image: image) ?? image
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func preferencesChanged() {
|
||||||
|
if isGrayscale != Preferences.shared.grayscaleImages {
|
||||||
|
isGrayscale = Preferences.shared.grayscaleImages
|
||||||
|
let image = if isGrayscale {
|
||||||
|
ImageGrayscalifier.convert(url: url, image: originalImage)
|
||||||
|
} else {
|
||||||
|
originalImage
|
||||||
|
}
|
||||||
|
if let image {
|
||||||
|
self.image = image
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var activityItemsForSharing: [Any] {
|
||||||
|
if let data = originalData ?? image.pngData() {
|
||||||
|
return [ImageActivityItemSource(data: data, url: url, image: image)]
|
||||||
|
} else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
//
|
||||||
|
// GrayscalableVideoGalleryContentViewController.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 11/21/24.
|
||||||
|
// Copyright © 2024 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import GalleryVC
|
||||||
|
import AVFoundation
|
||||||
|
|
||||||
|
class GrayscalableVideoGalleryContentViewController: GalleryVC.VideoGalleryContentViewController {
|
||||||
|
private var audioSessionToken: AudioSessionCoordinator.Token?
|
||||||
|
private var isGrayscale: Bool
|
||||||
|
private var isFirstAppearance = true
|
||||||
|
|
||||||
|
override init(url: URL, caption: String?) {
|
||||||
|
self.isGrayscale = Preferences.shared.grayscaleImages
|
||||||
|
|
||||||
|
super.init(url: url, caption: caption)
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override class func createItem(asset: AVAsset) -> AVPlayerItem {
|
||||||
|
let item = AVPlayerItem(asset: asset)
|
||||||
|
if Preferences.shared.grayscaleImages {
|
||||||
|
#if os(visionOS)
|
||||||
|
#warning("Use async AVVideoComposition CIFilter initializer")
|
||||||
|
#else
|
||||||
|
let filter = CIFilter(name: "CIColorMonochrome")!
|
||||||
|
filter.setValue(CIColor(red: 0.85, green: 0.85, blue: 0.85), forKey: "inputColor")
|
||||||
|
filter.setValue(1.0, forKey: "inputIntensity")
|
||||||
|
|
||||||
|
item.videoComposition = AVVideoComposition(asset: asset, applyingCIFiltersWithHandler: { request in
|
||||||
|
filter.setValue(request.sourceImage, forKey: "inputImage")
|
||||||
|
request.finish(with: filter.outputImage!, context: nil)
|
||||||
|
})
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func preferencesChanged() {
|
||||||
|
if isGrayscale != Preferences.shared.grayscaleImages {
|
||||||
|
let isPlaying = player.rate > 0
|
||||||
|
isGrayscale = Preferences.shared.grayscaleImages
|
||||||
|
replaceCurrentItem(with: Self.createItem(asset: item.asset))
|
||||||
|
if isPlaying {
|
||||||
|
player.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func galleryContentDidAppear() {
|
||||||
|
super.galleryContentDidAppear()
|
||||||
|
|
||||||
|
let wasFirstAppearance = isFirstAppearance
|
||||||
|
isFirstAppearance = false
|
||||||
|
|
||||||
|
audioSessionToken = AudioSessionCoordinator.shared.beginPlayback(mode: .video) {
|
||||||
|
if wasFirstAppearance {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.player.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func galleryContentWillDisappear() {
|
||||||
|
super.galleryContentWillDisappear()
|
||||||
|
|
||||||
|
if let audioSessionToken {
|
||||||
|
AudioSessionCoordinator.shared.endPlayback(token: audioSessionToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -34,7 +34,7 @@ class ImageGalleryDataSource: GalleryDataSource {
|
||||||
} else {
|
} else {
|
||||||
nil
|
nil
|
||||||
}
|
}
|
||||||
return ImageGalleryContentViewController(
|
return GrayscalableImageGalleryContentViewController(
|
||||||
url: url,
|
url: url,
|
||||||
caption: nil,
|
caption: nil,
|
||||||
originalData: entry.data,
|
originalData: entry.data,
|
||||||
|
@ -52,7 +52,7 @@ class ImageGalleryDataSource: GalleryDataSource {
|
||||||
} else {
|
} else {
|
||||||
nil
|
nil
|
||||||
}
|
}
|
||||||
return ImageGalleryContentViewController(
|
return GrayscalableImageGalleryContentViewController(
|
||||||
url: self.url,
|
url: self.url,
|
||||||
caption: nil,
|
caption: nil,
|
||||||
originalData: data,
|
originalData: data,
|
||||||
|
|
|
@ -33,7 +33,7 @@ class StatusAttachmentsGalleryDataSource: GalleryDataSource {
|
||||||
case .image:
|
case .image:
|
||||||
if let view = attachmentView(for: attachment),
|
if let view = attachmentView(for: attachment),
|
||||||
let image = view.attachmentImage {
|
let image = view.attachmentImage {
|
||||||
return ImageGalleryContentViewController(
|
return GrayscalableImageGalleryContentViewController(
|
||||||
url: attachment.url,
|
url: attachment.url,
|
||||||
caption: attachment.description,
|
caption: attachment.description,
|
||||||
originalData: view.originalData,
|
originalData: view.originalData,
|
||||||
|
@ -49,7 +49,7 @@ class StatusAttachmentsGalleryDataSource: GalleryDataSource {
|
||||||
} else {
|
} else {
|
||||||
nil
|
nil
|
||||||
}
|
}
|
||||||
return ImageGalleryContentViewController(
|
return GrayscalableImageGalleryContentViewController(
|
||||||
url: attachment.url,
|
url: attachment.url,
|
||||||
caption: attachment.description,
|
caption: attachment.description,
|
||||||
originalData: entry.data,
|
originalData: entry.data,
|
||||||
|
@ -68,7 +68,7 @@ class StatusAttachmentsGalleryDataSource: GalleryDataSource {
|
||||||
} else {
|
} else {
|
||||||
nil
|
nil
|
||||||
}
|
}
|
||||||
return ImageGalleryContentViewController(
|
return GrayscalableImageGalleryContentViewController(
|
||||||
url: attachment.url,
|
url: attachment.url,
|
||||||
caption: attachment.description,
|
caption: attachment.description,
|
||||||
originalData: data,
|
originalData: data,
|
||||||
|
@ -91,10 +91,10 @@ class StatusAttachmentsGalleryDataSource: GalleryDataSource {
|
||||||
}
|
}
|
||||||
return GifvGalleryContentViewController(controller: controller, url: attachment.url, caption: attachment.description)
|
return GifvGalleryContentViewController(controller: controller, url: attachment.url, caption: attachment.description)
|
||||||
case .video:
|
case .video:
|
||||||
return VideoGalleryContentViewController(url: attachment.url, caption: attachment.description)
|
return GrayscalableVideoGalleryContentViewController(url: attachment.url, caption: attachment.description)
|
||||||
case .audio:
|
case .audio:
|
||||||
// TODO: use separate content VC with audio visualization?
|
// TODO: use separate content VC with audio visualization?
|
||||||
return VideoGalleryContentViewController(url: attachment.url, caption: attachment.description)
|
return GrayscalableVideoGalleryContentViewController(url: attachment.url, caption: attachment.description)
|
||||||
case .unknown:
|
case .unknown:
|
||||||
return LoadingGalleryContentViewController(caption: nil) {
|
return LoadingGalleryContentViewController(caption: nil) {
|
||||||
do {
|
do {
|
||||||
|
|
|
@ -467,12 +467,12 @@ extension AttachmentView: UIContextMenuInteractionDelegate {
|
||||||
return UIContextMenuConfiguration { [unowned self] () -> UIViewController? in
|
return UIContextMenuConfiguration { [unowned self] () -> UIViewController? in
|
||||||
if self.attachment.kind == .image,
|
if self.attachment.kind == .image,
|
||||||
let image {
|
let image {
|
||||||
return ImageGalleryContentViewController(url: self.attachment.url, caption: nil, originalData: nil, image: image, gifController: self.gifController)
|
return GrayscalableImageGalleryContentViewController(url: self.attachment.url, caption: nil, originalData: nil, image: image, gifController: self.gifController)
|
||||||
} else if self.attachment.kind == .gifv,
|
} else if self.attachment.kind == .gifv,
|
||||||
let gifvView {
|
let gifvView {
|
||||||
return GifvGalleryContentViewController(controller: gifvView.controller, url: self.attachment.url, caption: nil)
|
return GifvGalleryContentViewController(controller: gifvView.controller, url: self.attachment.url, caption: nil)
|
||||||
} else if self.attachment.kind == .video || self.attachment.kind == .audio {
|
} else if self.attachment.kind == .video || self.attachment.kind == .audio {
|
||||||
let vc = VideoGalleryContentViewController(url: self.attachment.url, caption: nil)
|
let vc = GrayscalableVideoGalleryContentViewController(url: self.attachment.url, caption: nil)
|
||||||
vc.player.isMuted = true
|
vc.player.isMuted = true
|
||||||
return vc
|
return vc
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue