Merge branch 'develop' into compose-redesign

This commit is contained in:
Shadowfacts 2024-11-21 19:29:03 -05:00
commit 8cc9849b36
14 changed files with 284 additions and 219 deletions

View File

@ -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)
]), ]),

View File

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

View File

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

View File

@ -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()
} }

View File

@ -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?

View File

@ -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()
} }
}) })

View File

@ -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
} }
}) })

View File

@ -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 */,

View File

@ -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 []
}
}
}

View File

@ -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)
}
}
}

View File

@ -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,

View File

@ -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 {

View File

@ -30,7 +30,9 @@ class AccountDisplayAndUserNameLabel: EmojiLabel {
private func makeAttributedText(state: State) -> NSAttributedString { private func makeAttributedText(state: State) -> NSAttributedString {
let s = NSMutableAttributedString() let s = NSMutableAttributedString()
s.append(NSAttributedString(string: state.displayName, attributes: [ // U+2068 FIRST-STRONG ISOLATE and U+2069 POP DIRECTIONAL ISOLATE
// to prevent bidi text in the display name influencing the username
s.append(NSAttributedString(string: "\u{2068}\(state.displayName)\u{2069}", attributes: [
.font: UIFont(descriptor: baseFont.addingAttributes([ .font: UIFont(descriptor: baseFont.addingAttributes([
.traits: [ .traits: [
UIFontDescriptor.TraitKey.weight: UIFont.Weight.semibold.rawValue, UIFontDescriptor.TraitKey.weight: UIFont.Weight.semibold.rawValue,

View File

@ -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 {