Add an ALT button to the media preview to display alt text

This commit is contained in:
Jed Fox 2022-11-26 09:39:24 -05:00
parent e8e15f3a0e
commit 582d1cf295
No known key found for this signature in database
GPG Key ID: 0B61D18EA54B47E1
8 changed files with 126 additions and 7 deletions

View File

@ -99,6 +99,7 @@
85904C02293BC0EB0011C817 /* ImageProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85904C01293BC0EB0011C817 /* ImageProvider.swift */; };
85904C04293BC1940011C817 /* URLActivityItemWithMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85904C03293BC1940011C817 /* URLActivityItemWithMetadata.swift */; };
85BC11B1292FF92C00E191CD /* HUDButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85BC11B0292FF92C00E191CD /* HUDButton.swift */; };
85BC11B32932414900E191CD /* AltViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85BC11B22932414900E191CD /* AltViewController.swift */; };
87FFDA5D898A5C42ADCB35E7 /* Pods_Mastodon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A4ABE34829701A4496C5BB64 /* Pods_Mastodon.framework */; };
C24C97032922F30500BAE8CB /* RefreshControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C24C97022922F30500BAE8CB /* RefreshControl.swift */; };
D87BFC8B291D5C6B00FEE264 /* MastodonLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */; };
@ -622,6 +623,7 @@
85904C01293BC0EB0011C817 /* ImageProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageProvider.swift; sourceTree = "<group>"; };
85904C03293BC1940011C817 /* URLActivityItemWithMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLActivityItemWithMetadata.swift; sourceTree = "<group>"; };
85BC11B0292FF92C00E191CD /* HUDButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HUDButton.swift; sourceTree = "<group>"; };
85BC11B22932414900E191CD /* AltViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AltViewController.swift; sourceTree = "<group>"; };
8850E70A1D5FF51432E43653 /* Pods-Mastodon-MastodonUITests.asdk - release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.asdk - release.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.asdk - release.xcconfig"; sourceTree = "<group>"; };
8E79CCBE51FBC3F7FE8CF49F /* Pods-MastodonTests.release snapshot.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonTests.release snapshot.xcconfig"; path = "Target Support Files/Pods-MastodonTests/Pods-MastodonTests.release snapshot.xcconfig"; sourceTree = "<group>"; };
8ED8C4B1F1BA2DCFF2926BB1 /* Pods-Mastodon-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon-NotificationService/Pods-Mastodon-NotificationService.debug.xcconfig"; sourceTree = "<group>"; };
@ -1978,6 +1980,7 @@
DB6180E1263919780018D199 /* Paging */,
85BC11B0292FF92C00E191CD /* HUDButton.swift */,
DB6180DC263918E30018D199 /* MediaPreviewViewController.swift */,
85BC11B22932414900E191CD /* AltViewController.swift */,
DB6180F926391F2E0018D199 /* MediaPreviewViewModel.swift */,
);
path = MediaPreview;
@ -3539,6 +3542,7 @@
DB9D6BFF25E4F5940051B173 /* ProfileViewController.swift in Sources */,
2D4AD8A226316CD200613EFC /* SelectedAccountSection.swift in Sources */,
DB3EA8F1281B9EF600598866 /* DiscoveryCommunityViewModel+Diffable.swift in Sources */,
85BC11B32932414900E191CD /* AltViewController.swift in Sources */,
DB63F775279A997D00455B82 /* NotificationTableViewCell+ViewModel.swift in Sources */,
DB98EB5927B109890082E365 /* ReportSupplementaryViewController.swift in Sources */,
DB0617EB277EF3820030EE79 /* GradientBorderView.swift in Sources */,

View File

@ -0,0 +1,72 @@
//
// AltViewController.swift
// Mastodon
//
// Created by Jed Fox on 2022-11-26.
//
import SwiftUI
class AltViewController: UIViewController {
var alt: String?
let label = UILabel()
convenience init(alt: String?, sourceView: UIView?) {
self.init(nibName: nil, bundle: nil)
self.alt = alt
self.modalPresentationStyle = .popover
self.popoverPresentationController?.delegate = self
self.popoverPresentationController?.permittedArrowDirections = .up
self.popoverPresentationController?.sourceView = sourceView
self.overrideUserInterfaceStyle = .dark
}
@objc override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
@MainActor required dynamic init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.lineBreakStrategy = .standard
label.font = .preferredFont(forTextStyle: .callout)
label.text = alt ?? "ummmmmmm tbd but you shouldnt see this"
view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
NSLayoutConstraint.activate(
NSLayoutConstraint.constraints(withVisualFormat: "V:|-[label]-|", metrics: nil, views: ["label": label])
)
NSLayoutConstraint.activate(
NSLayoutConstraint.constraints(withVisualFormat: "H:|-[label]-|", metrics: nil, views: ["label": label])
)
NSLayoutConstraint.activate([
label.widthAnchor.constraint(lessThanOrEqualToConstant: 400),
])
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
UIView.performWithoutAnimation {
preferredContentSize = CGSize(
width: label.intrinsicContentSize.width + view.layoutMargins.left + view.layoutMargins.right,
height: label.intrinsicContentSize.height + view.layoutMargins.top + view.layoutMargins.bottom
)
}
}
}
// MARK: UIPopoverPresentationControllerDelegate
extension AltViewController: UIPopoverPresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
.none
}
}

View File

@ -25,7 +25,9 @@ class HUDButton: UIView {
let button: UIButton = {
let button = HighlightDimmableButton()
button.expandEdgeInsets = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10)
button.contentEdgeInsets = .constant(7)
button.imageView?.tintColor = .label
button.titleLabel?.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .bold))
return button
}()
@ -56,4 +58,9 @@ class HUDButton: UIView {
heightAnchor.constraint(equalToConstant: HUDButton.height).priority(.defaultHigh),
])
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
button.titleLabel?.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .bold))
}
}

View File

@ -110,6 +110,10 @@ extension MediaPreviewImageViewController: MediaPreviewPage {
}
}
}
var altText: String? {
viewModel.item.altText
}
}
// MARK: - ImageAnalysisInteractionDelegate

View File

@ -29,6 +29,10 @@ final class MediaPreviewViewController: UIViewController, NeedsDependency {
button.setImage(UIImage(systemName: "xmark", withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .bold))!, for: .normal)
}
let altButton = HUDButton { button in
button.setTitle("ALT", for: .normal)
}
deinit {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
}
@ -58,7 +62,13 @@ extension MediaPreviewViewController {
closeButton.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
closeButton.widthAnchor.constraint(equalToConstant: HUDButton.height).priority(.defaultHigh),
])
view.addSubview(altButton)
NSLayoutConstraint.activate([
altButton.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: 12),
altButton.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
])
viewModel.mediaPreviewImageViewControllerDelegate = self
pagingViewController.interPageSpacing = 10
@ -66,7 +76,8 @@ extension MediaPreviewViewController {
pagingViewController.dataSource = viewModel
closeButton.button.addTarget(self, action: #selector(MediaPreviewViewController.closeButtonPressed(_:)), for: .touchUpInside)
altButton.button.addTarget(self, action: #selector(MediaPreviewViewController.altButtonPressed(_:)), for: .touchUpInside)
// bind view model
viewModel.$currentPage
.receive(on: DispatchQueue.main)
@ -144,7 +155,12 @@ extension MediaPreviewViewController {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
dismiss(animated: true, completion: nil)
}
@objc private func altButtonPressed(_ sender: UIButton) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
present(AltViewController(alt: viewModel.viewControllers[viewModel.currentPage].altText, sourceView: sender), animated: true)
}
}
// MARK: - MediaPreviewingViewController

View File

@ -14,6 +14,7 @@ import MastodonCore
protocol MediaPreviewPage: UIViewController {
func setShowingChrome(_ showingChrome: Bool)
var altText: String? { get }
}
final class MediaPreviewViewModel: NSObject {
@ -27,9 +28,9 @@ final class MediaPreviewViewModel: NSObject {
@Published var currentPage: Int
@Published var showingChrome = true
// output
let viewControllers: [UIViewController]
let viewControllers: [MediaPreviewPage]
private var disposeBag: Set<AnyCancellable> = []
@ -65,7 +66,8 @@ final class MediaPreviewViewModel: NSObject {
context: context,
item: .gif(.init(
assetURL: attachment.assetURL.flatMap { URL(string: $0) },
previewURL: attachment.previewURL.flatMap { URL(string: $0) }
previewURL: attachment.previewURL.flatMap { URL(string: $0) },
altText: attachment.altDescription
))
)
viewController.viewModel = viewModel
@ -76,7 +78,8 @@ final class MediaPreviewViewModel: NSObject {
context: context,
item: .video(.init(
assetURL: attachment.assetURL.flatMap { URL(string: $0) },
previewURL: attachment.previewURL.flatMap { URL(string: $0) }
previewURL: attachment.previewURL.flatMap { URL(string: $0) },
altText: attachment.altDescription
))
)
viewController.viewModel = viewModel

View File

@ -111,6 +111,10 @@ extension MediaPreviewVideoViewController: MediaPreviewPage {
func setShowingChrome(_ showingChrome: Bool) {
// TODO: does this do anything?
}
var altText: String? {
viewModel.item.altText
}
}
// MARK: - AVPlayerViewControllerDelegate

View File

@ -125,17 +125,26 @@ extension MediaPreviewVideoViewModel {
case .gif(let mediaContext): return mediaContext.assetURL
}
}
var altText: String? {
switch self {
case .video(let mediaContext): return mediaContext.altText
case .gif(let mediaContext): return mediaContext.altText
}
}
}
struct RemoteVideoContext {
let assetURL: URL?
let previewURL: URL?
let altText: String?
// let thumbnail: UIImage?
}
struct RemoteGIFContext {
let assetURL: URL?
let previewURL: URL?
let altText: String?
}
}