mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-25 09:41:00 +00:00
VoiceOver support wip
This commit is contained in:
parent
00de105df4
commit
a2cd4c3386
16 changed files with 265 additions and 11 deletions
40
Extensions/Attachment+Extensions.swift
Normal file
40
Extensions/Attachment+Extensions.swift
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
// Copyright © 2021 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Mastodon
|
||||||
|
|
||||||
|
extension Attachment.AttachmentType {
|
||||||
|
var accessibilityName: String {
|
||||||
|
switch self {
|
||||||
|
case .image, .gifv:
|
||||||
|
return NSLocalizedString("attachment.type.image", comment: "")
|
||||||
|
case .video:
|
||||||
|
return NSLocalizedString("attachment.type.video", comment: "")
|
||||||
|
case .audio:
|
||||||
|
return NSLocalizedString("attachment.type.audio", comment: "")
|
||||||
|
case .unknown:
|
||||||
|
return NSLocalizedString("attachment.type.unknown", comment: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func accessibilityNames(count: Int) -> String {
|
||||||
|
if count == 1 {
|
||||||
|
return accessibilityName
|
||||||
|
}
|
||||||
|
|
||||||
|
let format: String
|
||||||
|
|
||||||
|
switch self {
|
||||||
|
case .image, .gifv:
|
||||||
|
format = NSLocalizedString("attachment.type.images-%ld", comment: "")
|
||||||
|
case .video:
|
||||||
|
format = NSLocalizedString("attachment.type.videos-%ld", comment: "")
|
||||||
|
case .audio:
|
||||||
|
format = NSLocalizedString("attachment.type.audios-%ld", comment: "")
|
||||||
|
case .unknown:
|
||||||
|
format = NSLocalizedString("attachment.type.unknowns-%ld", comment: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
return String.localizedStringWithFormat(format, count)
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,4 +25,13 @@ extension NSMutableAttributedString {
|
||||||
attachment.bounds = CGRect(x: 0, y: lineHeight * -0.25, width: lineHeight, height: lineHeight)
|
attachment.bounds = CGRect(x: 0, y: lineHeight * -0.25, width: lineHeight, height: lineHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func appendWithSeparator(_ string: NSAttributedString) {
|
||||||
|
append(.init(string: .separator))
|
||||||
|
append(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendWithSeparator(_ string: String) {
|
||||||
|
appendWithSeparator(.init(string: string))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,10 @@ import Mastodon
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
extension String {
|
extension String {
|
||||||
|
static var separator: Self {
|
||||||
|
(Locale.autoupdatingCurrent.groupingSeparator ?? ",").appending(" ")
|
||||||
|
}
|
||||||
|
|
||||||
func height(width: CGFloat, font: UIFont) -> CGFloat {
|
func height(width: CGFloat, font: UIFont) -> CGFloat {
|
||||||
(self as NSString).boundingRect(
|
(self as NSString).boundingRect(
|
||||||
with: CGSize(width: width, height: .greatestFiniteMagnitude),
|
with: CGSize(width: width, height: .greatestFiniteMagnitude),
|
||||||
|
@ -61,9 +65,3 @@ extension String {
|
||||||
append(Self.separator.appending(string))
|
append(Self.separator.appending(string))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension String {
|
|
||||||
static var separator: Self {
|
|
||||||
Locale.autoupdatingCurrent.groupingSeparator ?? ","
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -49,14 +49,26 @@
|
||||||
"attachment.edit.thumbnail.prompt" = "Drag the circle on the preview to choose the focal point which will always be in view on all thumbnails";
|
"attachment.edit.thumbnail.prompt" = "Drag the circle on the preview to choose the focal point which will always be in view on all thumbnails";
|
||||||
"attachment.sensitive-content" = "Sensitive content";
|
"attachment.sensitive-content" = "Sensitive content";
|
||||||
"attachment.media-hidden" = "Media hidden";
|
"attachment.media-hidden" = "Media hidden";
|
||||||
|
"attachment.type.image" = "Image";
|
||||||
|
"attachment.type.images-%ld" = "%ld images";
|
||||||
|
"attachment.type.audio" = "Audio";
|
||||||
|
"attachment.type.audios-%ld" = "%ld audio files";
|
||||||
|
"attachment.type.video" = "Video";
|
||||||
|
"attachment.type.videos-%ld" = "%ld videos";
|
||||||
|
"attachment.type.unknown" = "Attachment";
|
||||||
|
"attachment.type.unknown-%ld" = "%ld attachments";
|
||||||
"attachment.unable-to-export-media" = "Unable to export media";
|
"attachment.unable-to-export-media" = "Unable to export media";
|
||||||
"bookmarks" = "Bookmarks";
|
"bookmarks" = "Bookmarks";
|
||||||
|
"card.link.accessibility-label" = "Link";
|
||||||
"camera-access.title" = "Camera access needed";
|
"camera-access.title" = "Camera access needed";
|
||||||
"camera-access.description" = "Open system settings to allow camera access";
|
"camera-access.description" = "Open system settings to allow camera access";
|
||||||
"camera-access.open-system-settings" = "Open system settings";
|
"camera-access.open-system-settings" = "Open system settings";
|
||||||
"cancel" = "Cancel";
|
"cancel" = "Cancel";
|
||||||
"compose.add-button-accessibility-label.post" = "Add another post";
|
"compose.add-button-accessibility-label.post" = "Add another post";
|
||||||
"compose.add-button-accessibility-label.toot" = "Add another toot";
|
"compose.add-button-accessibility-label.toot" = "Add another toot";
|
||||||
|
"compose.attachment.cancel-upload.accessibility-label" = "Cancel uploading attachment";
|
||||||
|
"compose.attachment.edit" = "Edit attachment";
|
||||||
|
"compose.attachment.remove" = "Remove attachment";
|
||||||
"compose.attachment.uploading" = "Uploading";
|
"compose.attachment.uploading" = "Uploading";
|
||||||
"compose.attachments-button.accessibility-label" = "Add attachment";
|
"compose.attachments-button.accessibility-label" = "Add attachment";
|
||||||
"compose.attachments-will-be-discarded" = "Attachments will be discarded when changing accounts";
|
"compose.attachments-will-be-discarded" = "Attachments will be discarded when changing accounts";
|
||||||
|
@ -68,9 +80,9 @@
|
||||||
"compose.emoji-button" = "Emoji picker";
|
"compose.emoji-button" = "Emoji picker";
|
||||||
"compose.mark-media-sensitive" = "Mark media as sensitive";
|
"compose.mark-media-sensitive" = "Mark media as sensitive";
|
||||||
"compose.photo-library" = "Photo Library";
|
"compose.photo-library" = "Photo Library";
|
||||||
|
"compose.poll.accessibility.multiple-choices-allowed" = "Mutliple choices allowed";
|
||||||
"compose.poll.add-choice" = "Add a choice";
|
"compose.poll.add-choice" = "Add a choice";
|
||||||
"compose.poll.allow-multiple-choices" = "Allow multiple choices";
|
"compose.poll.allow-multiple-choices" = "Allow multiple choices";
|
||||||
"compose.poll.option-%ld" = "Option %ld";
|
|
||||||
"compose.poll-button.accessibility-label" = "Add a poll";
|
"compose.poll-button.accessibility-label" = "Add a poll";
|
||||||
"compose.prompt" = "What's on your mind?";
|
"compose.prompt" = "What's on your mind?";
|
||||||
"compose.take-photo-or-video" = "Take Photo or Video";
|
"compose.take-photo-or-video" = "Take Photo or Video";
|
||||||
|
@ -221,6 +233,9 @@
|
||||||
"share-extension-error.no-account-found" = "No account found";
|
"share-extension-error.no-account-found" = "No account found";
|
||||||
"status.bookmark" = "Bookmark";
|
"status.bookmark" = "Bookmark";
|
||||||
"status.content-warning-abbreviation" = "CW";
|
"status.content-warning-abbreviation" = "CW";
|
||||||
|
"status.content-warning.accessibility" = "Content warning";
|
||||||
|
"status.content-warning.accessibility.closed" = "Closed";
|
||||||
|
"status.content-warning.accessibility.opened" = "Opened";
|
||||||
"status.delete" = "Delete";
|
"status.delete" = "Delete";
|
||||||
"status.delete.confirm.post" = "Are you sure you want to delete this post?";
|
"status.delete.confirm.post" = "Are you sure you want to delete this post?";
|
||||||
"status.delete.confirm.toot" = "Are you sure you want to delete this toot?";
|
"status.delete.confirm.toot" = "Are you sure you want to delete this toot?";
|
||||||
|
@ -231,6 +246,8 @@
|
||||||
"status.pin" = "Pin on profile";
|
"status.pin" = "Pin on profile";
|
||||||
"status.pinned.post" = "Pinned post";
|
"status.pinned.post" = "Pinned post";
|
||||||
"status.pinned.toot" = "Pinned toot";
|
"status.pinned.toot" = "Pinned toot";
|
||||||
|
"status.poll.accessibility-label" = "Poll";
|
||||||
|
"status.poll.option-%ld" = "Option %ld";
|
||||||
"status.poll.vote" = "Vote";
|
"status.poll.vote" = "Vote";
|
||||||
"status.poll.time-left" = "%@ left";
|
"status.poll.time-left" = "%@ left";
|
||||||
"status.poll.refresh" = "Refresh";
|
"status.poll.refresh" = "Refresh";
|
||||||
|
|
|
@ -13,6 +13,10 @@
|
||||||
D00702362555F4C500F38136 /* ConversationContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00702352555F4C500F38136 /* ConversationContentConfiguration.swift */; };
|
D00702362555F4C500F38136 /* ConversationContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00702352555F4C500F38136 /* ConversationContentConfiguration.swift */; };
|
||||||
D007023E25562A2800F38136 /* ConversationAvatarsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D007023D25562A2800F38136 /* ConversationAvatarsView.swift */; };
|
D007023E25562A2800F38136 /* ConversationAvatarsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D007023D25562A2800F38136 /* ConversationAvatarsView.swift */; };
|
||||||
D0070252255921B100F38136 /* AccountFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0070251255921B100F38136 /* AccountFieldView.swift */; };
|
D0070252255921B100F38136 /* AccountFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0070251255921B100F38136 /* AccountFieldView.swift */; };
|
||||||
|
D00CB22A25C92C0F008EF267 /* Attachment+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00CB22925C92C0F008EF267 /* Attachment+Extensions.swift */; };
|
||||||
|
D00CB23325C92F2D008EF267 /* Attachment+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00CB22925C92C0F008EF267 /* Attachment+Extensions.swift */; };
|
||||||
|
D00CB23825C93047008EF267 /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46A24F76169001EBDBB /* String+Extensions.swift */; };
|
||||||
|
D00CB23D25C9305D008EF267 /* NSMutableAttributedString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */; };
|
||||||
D00CB2ED2533ACC00080096B /* StatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00CB2EC2533ACC00080096B /* StatusView.swift */; };
|
D00CB2ED2533ACC00080096B /* StatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00CB2EC2533ACC00080096B /* StatusView.swift */; };
|
||||||
D015B13525A812DD006D88A8 /* AttachmentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41E224F8889700D55A2D /* AttachmentsView.swift */; };
|
D015B13525A812DD006D88A8 /* AttachmentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41E224F8889700D55A2D /* AttachmentsView.swift */; };
|
||||||
D015B13A25A812E6006D88A8 /* AttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1F224F8EE8C001B0F04 /* AttachmentView.swift */; };
|
D015B13A25A812E6006D88A8 /* AttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1F224F8EE8C001B0F04 /* AttachmentView.swift */; };
|
||||||
|
@ -227,6 +231,7 @@
|
||||||
D00702352555F4C500F38136 /* ConversationContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationContentConfiguration.swift; sourceTree = "<group>"; };
|
D00702352555F4C500F38136 /* ConversationContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationContentConfiguration.swift; sourceTree = "<group>"; };
|
||||||
D007023D25562A2800F38136 /* ConversationAvatarsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationAvatarsView.swift; sourceTree = "<group>"; };
|
D007023D25562A2800F38136 /* ConversationAvatarsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationAvatarsView.swift; sourceTree = "<group>"; };
|
||||||
D0070251255921B100F38136 /* AccountFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFieldView.swift; sourceTree = "<group>"; };
|
D0070251255921B100F38136 /* AccountFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFieldView.swift; sourceTree = "<group>"; };
|
||||||
|
D00CB22925C92C0F008EF267 /* Attachment+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Attachment+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
D00CB2EC2533ACC00080096B /* StatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusView.swift; sourceTree = "<group>"; };
|
D00CB2EC2533ACC00080096B /* StatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusView.swift; sourceTree = "<group>"; };
|
||||||
D01EF22325182B1F00650C6B /* AccountHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountHeaderView.swift; sourceTree = "<group>"; };
|
D01EF22325182B1F00650C6B /* AccountHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountHeaderView.swift; sourceTree = "<group>"; };
|
||||||
D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchFallthroughTextView.swift; sourceTree = "<group>"; };
|
D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchFallthroughTextView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -714,6 +719,7 @@
|
||||||
D0C7D46824F76169001EBDBB /* Extensions */ = {
|
D0C7D46824F76169001EBDBB /* Extensions */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D00CB22925C92C0F008EF267 /* Attachment+Extensions.swift */,
|
||||||
D05E688425B55AE8001FB2C6 /* AVURLAsset+Extensions.swift */,
|
D05E688425B55AE8001FB2C6 /* AVURLAsset+Extensions.swift */,
|
||||||
D0F0B135251AA12700942152 /* CollectionItem+Extensions.swift */,
|
D0F0B135251AA12700942152 /* CollectionItem+Extensions.swift */,
|
||||||
D0D2AC3825BBEC0F003D5DF2 /* CollectionSection+Extensions.swift */,
|
D0D2AC3825BBEC0F003D5DF2 /* CollectionSection+Extensions.swift */,
|
||||||
|
@ -1028,6 +1034,7 @@
|
||||||
D021A61425C36BFB008A0C0D /* IdentityView.swift in Sources */,
|
D021A61425C36BFB008A0C0D /* IdentityView.swift in Sources */,
|
||||||
D06BC5E625202AD90079541D /* ProfileViewController.swift in Sources */,
|
D06BC5E625202AD90079541D /* ProfileViewController.swift in Sources */,
|
||||||
D0D2AC4D25BCD2A9003D5DF2 /* TagTableViewCell.swift in Sources */,
|
D0D2AC4D25BCD2A9003D5DF2 /* TagTableViewCell.swift in Sources */,
|
||||||
|
D00CB22A25C92C0F008EF267 /* Attachment+Extensions.swift in Sources */,
|
||||||
D0D2AC5325BCD2BA003D5DF2 /* TagContentConfiguration.swift in Sources */,
|
D0D2AC5325BCD2BA003D5DF2 /* TagContentConfiguration.swift in Sources */,
|
||||||
D08B8D72254246E200B1EBEF /* PollView.swift in Sources */,
|
D08B8D72254246E200B1EBEF /* PollView.swift in Sources */,
|
||||||
D035F8A925B9155900DC75ED /* NewStatusButtonView.swift in Sources */,
|
D035F8A925B9155900DC75ED /* NewStatusButtonView.swift in Sources */,
|
||||||
|
@ -1098,10 +1105,12 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
D08E52A6257C61C000FA2C5F /* ShareExtensionNavigationViewController.swift in Sources */,
|
D08E52A6257C61C000FA2C5F /* ShareExtensionNavigationViewController.swift in Sources */,
|
||||||
|
D00CB23825C93047008EF267 /* String+Extensions.swift in Sources */,
|
||||||
D059373425AAEA7000754FDF /* CompositionPollView.swift in Sources */,
|
D059373425AAEA7000754FDF /* CompositionPollView.swift in Sources */,
|
||||||
D021A67B25C3E32A008A0C0D /* PlayerView.swift in Sources */,
|
D021A67B25C3E32A008A0C0D /* PlayerView.swift in Sources */,
|
||||||
D021A69025C3E4B8008A0C0D /* EmojiContentConfiguration.swift in Sources */,
|
D021A69025C3E4B8008A0C0D /* EmojiContentConfiguration.swift in Sources */,
|
||||||
D08E52D2257C811200FA2C5F /* ShareExtensionError+Extensions.swift in Sources */,
|
D08E52D2257C811200FA2C5F /* ShareExtensionError+Extensions.swift in Sources */,
|
||||||
|
D00CB23D25C9305D008EF267 /* NSMutableAttributedString+Extensions.swift in Sources */,
|
||||||
D0E9F9AB258450B300EF503D /* CompositionInputAccessoryView.swift in Sources */,
|
D0E9F9AB258450B300EF503D /* CompositionInputAccessoryView.swift in Sources */,
|
||||||
D025B14E25C4E482001C69A8 /* ImageCacheConfiguration.swift in Sources */,
|
D025B14E25C4E482001C69A8 /* ImageCacheConfiguration.swift in Sources */,
|
||||||
D05936D025A8D79800754FDF /* EditAttachmentViewController.swift in Sources */,
|
D05936D025A8D79800754FDF /* EditAttachmentViewController.swift in Sources */,
|
||||||
|
@ -1123,6 +1132,7 @@
|
||||||
D0FCC106259C4E62000B67DF /* NewStatusViewController.swift in Sources */,
|
D0FCC106259C4E62000B67DF /* NewStatusViewController.swift in Sources */,
|
||||||
D036EBB3259FE28800EC1CFC /* UIColor+Extensions.swift in Sources */,
|
D036EBB3259FE28800EC1CFC /* UIColor+Extensions.swift in Sources */,
|
||||||
D088406E25AFBBE200BB749B /* EmojiPickerViewController.swift in Sources */,
|
D088406E25AFBBE200BB749B /* EmojiPickerViewController.swift in Sources */,
|
||||||
|
D00CB23325C92F2D008EF267 /* Attachment+Extensions.swift in Sources */,
|
||||||
D025B14725C4D26B001C69A8 /* ImageCacheSerializer.swift in Sources */,
|
D025B14725C4D26B001C69A8 /* ImageCacheSerializer.swift in Sources */,
|
||||||
D036EBB8259FE29800EC1CFC /* Status+Extensions.swift in Sources */,
|
D036EBB8259FE29800EC1CFC /* Status+Extensions.swift in Sources */,
|
||||||
D021A6A625C3E584008A0C0D /* EditAttachmentView.swift in Sources */,
|
D021A6A625C3E584008A0C0D /* EditAttachmentView.swift in Sources */,
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import AVFoundation
|
import AVFoundation
|
||||||
import Kingfisher
|
import Kingfisher
|
||||||
|
import Mastodon
|
||||||
import UIKit
|
import UIKit
|
||||||
import ViewModels
|
import ViewModels
|
||||||
|
|
||||||
|
@ -133,11 +134,21 @@ final class ImageViewController: UIViewController {
|
||||||
player.play()
|
player.play()
|
||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var accessibilityLabel = viewModel.attachment.type.accessibilityName
|
||||||
|
|
||||||
|
if let description = viewModel.attachment.description {
|
||||||
|
accessibilityLabel.appendWithSeparator(description)
|
||||||
|
}
|
||||||
} else if let imageURL = imageURL {
|
} else if let imageURL = imageURL {
|
||||||
imageView.tag = imageURL.hashValue
|
imageView.tag = imageURL.hashValue
|
||||||
playerView.isHidden = true
|
playerView.isHidden = true
|
||||||
imageView.kf.setImage(with: imageURL)
|
imageView.kf.setImage(with: imageURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contentView.accessibilityLabel = viewModel?.attachment.type.accessibilityName
|
||||||
|
?? Attachment.AttachmentType.image.accessibilityName
|
||||||
|
contentView.isAccessibilityElement = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ final class AttachmentUploadView: UIView {
|
||||||
private var progressCancellable: AnyCancellable?
|
private var progressCancellable: AnyCancellable?
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
// swiftlint:disable:next function_body_length
|
||||||
init(viewModel: CompositionViewModel) {
|
init(viewModel: CompositionViewModel) {
|
||||||
self.viewModel = viewModel
|
self.viewModel = viewModel
|
||||||
|
|
||||||
|
@ -33,6 +34,8 @@ final class AttachmentUploadView: UIView {
|
||||||
cancelButton.titleLabel?.font = .preferredFont(forTextStyle: .callout)
|
cancelButton.titleLabel?.font = .preferredFont(forTextStyle: .callout)
|
||||||
cancelButton.setTitle(NSLocalizedString("cancel", comment: ""), for: .normal)
|
cancelButton.setTitle(NSLocalizedString("cancel", comment: ""), for: .normal)
|
||||||
cancelButton.addAction(UIAction { _ in viewModel.cancelUpload() }, for: .touchUpInside)
|
cancelButton.addAction(UIAction { _ in viewModel.cancelUpload() }, for: .touchUpInside)
|
||||||
|
cancelButton.accessibilityLabel =
|
||||||
|
NSLocalizedString("compose.attachment.cancel-upload.accessibility-label", comment: "")
|
||||||
|
|
||||||
addSubview(progressView)
|
addSubview(progressView)
|
||||||
progressView.translatesAutoresizingMaskIntoConstraints = false
|
progressView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
|
@ -130,6 +130,7 @@ private extension AttachmentView {
|
||||||
addSubview(selectionButton)
|
addSubview(selectionButton)
|
||||||
selectionButton.translatesAutoresizingMaskIntoConstraints = false
|
selectionButton.translatesAutoresizingMaskIntoConstraints = false
|
||||||
selectionButton.setBackgroundImage(.highlightedButtonBackground, for: .highlighted)
|
selectionButton.setBackgroundImage(.highlightedButtonBackground, for: .highlighted)
|
||||||
|
selectionButton.accessibilityLabel = NSLocalizedString("compose.attachment.edit", comment: "")
|
||||||
selectionButton.addAction(
|
selectionButton.addAction(
|
||||||
UIAction { [weak self] _ in
|
UIAction { [weak self] _ in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
@ -141,6 +142,7 @@ private extension AttachmentView {
|
||||||
addSubview(removeButton)
|
addSubview(removeButton)
|
||||||
removeButton.translatesAutoresizingMaskIntoConstraints = false
|
removeButton.translatesAutoresizingMaskIntoConstraints = false
|
||||||
removeButton.showsMenuAsPrimaryAction = true
|
removeButton.showsMenuAsPrimaryAction = true
|
||||||
|
removeButton.accessibilityLabel = NSLocalizedString("compose.attachment.remove", comment: "")
|
||||||
removeButton.menu = UIMenu(
|
removeButton.menu = UIMenu(
|
||||||
children: [
|
children: [
|
||||||
UIAction(
|
UIAction(
|
||||||
|
@ -210,5 +212,13 @@ private extension AttachmentView {
|
||||||
editIcon.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
|
editIcon.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
|
||||||
editIcon.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor)
|
editIcon.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor)
|
||||||
])
|
])
|
||||||
|
|
||||||
|
var accessibilityLabel = viewModel.attachment.type.accessibilityName
|
||||||
|
|
||||||
|
if let description = viewModel.attachment.description {
|
||||||
|
accessibilityLabel.appendWithSeparator(description)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.accessibilityLabel = accessibilityLabel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ final class AttachmentsView: UIView {
|
||||||
attachmentView.playing = viewModel.shouldShowAttachments && attachmentViewModel.shouldAutoplay
|
attachmentView.playing = viewModel.shouldShowAttachments && attachmentViewModel.shouldAutoplay
|
||||||
attachmentView.removeButton.isHidden = !viewModel.canRemoveAttachments
|
attachmentView.removeButton.isHidden = !viewModel.canRemoveAttachments
|
||||||
attachmentView.editIcon.isHidden = !viewModel.canRemoveAttachments
|
attachmentView.editIcon.isHidden = !viewModel.canRemoveAttachments
|
||||||
|
attachmentView.isAccessibilityElement = !viewModel.canRemoveAttachments
|
||||||
|
|
||||||
if viewModel.attachmentViewModels.count == 2 && index == 1
|
if viewModel.attachmentViewModels.count == 2 && index == 1
|
||||||
|| viewModel.attachmentViewModels.count == 3 && index != 0
|
|| viewModel.attachmentViewModels.count == 3 && index != 0
|
||||||
|
@ -66,6 +67,25 @@ final class AttachmentsView: UIView {
|
||||||
comment: ""),
|
comment: ""),
|
||||||
for: .normal)
|
for: .normal)
|
||||||
hideButtonBackground.isHidden = !viewModel.shouldShowHideAttachmentsButton
|
hideButtonBackground.isHidden = !viewModel.shouldShowHideAttachmentsButton
|
||||||
|
|
||||||
|
if curtain.isHidden {
|
||||||
|
let type: Attachment.AttachmentType
|
||||||
|
|
||||||
|
if viewModel.attachmentViewModels
|
||||||
|
.allSatisfy({ $0.attachment.type == .image || $0.attachment.type == .gifv }) {
|
||||||
|
type = .image
|
||||||
|
} else if viewModel.attachmentViewModels.allSatisfy({ $0.attachment.type == .video }) {
|
||||||
|
type = .video
|
||||||
|
} else if viewModel.attachmentViewModels.allSatisfy({ $0.attachment.type == .audio }) {
|
||||||
|
type = .audio
|
||||||
|
} else {
|
||||||
|
type = .unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
accessibilityLabel = type.accessibilityNames(count: viewModel.attachmentViewModels.count)
|
||||||
|
} else {
|
||||||
|
accessibilityLabel = curtainButton.title(for: .normal)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,9 @@ final class CardView: UIView {
|
||||||
didSet {
|
didSet {
|
||||||
guard let viewModel = viewModel else { return }
|
guard let viewModel = viewModel else { return }
|
||||||
|
|
||||||
|
var accessibilityLabel = NSLocalizedString("card.link.accessibility-label", comment: "")
|
||||||
|
.appendingWithSeparator(viewModel.title)
|
||||||
|
|
||||||
imageView.isHidden = viewModel.imageURL == nil
|
imageView.isHidden = viewModel.imageURL == nil
|
||||||
imageView.kf.setImage(with: viewModel.imageURL)
|
imageView.kf.setImage(with: viewModel.imageURL)
|
||||||
|
|
||||||
|
@ -30,6 +33,12 @@ final class CardView: UIView {
|
||||||
} else {
|
} else {
|
||||||
urlLabel.text = viewModel.url.host
|
urlLabel.text = viewModel.url.host
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let urlLabelText = urlLabel.text {
|
||||||
|
accessibilityLabel.appendWithSeparator(urlLabelText)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.accessibilityLabel = accessibilityLabel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +46,7 @@ final class CardView: UIView {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
initialSetup()
|
initialSetup()
|
||||||
|
setupAccessibility()
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(*, unavailable)
|
@available(*, unavailable)
|
||||||
|
@ -120,4 +130,8 @@ private extension CardView {
|
||||||
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor)
|
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor)
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setupAccessibility() {
|
||||||
|
isAccessibilityElement = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,7 +124,7 @@ private extension CompositionPollView {
|
||||||
option: option)
|
option: option)
|
||||||
|
|
||||||
optionView.textField.placeholder = String.localizedStringWithFormat(
|
optionView.textField.placeholder = String.localizedStringWithFormat(
|
||||||
NSLocalizedString("compose.poll.option-%ld", comment: ""),
|
NSLocalizedString("status.poll.option-%ld", comment: ""),
|
||||||
index + 1)
|
index + 1)
|
||||||
self.stackView.insertArrangedSubview(optionView, at: index)
|
self.stackView.insertArrangedSubview(optionView, at: index)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright © 2020 Metabolist. All rights reserved.
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
// swiftlint:disable file_length
|
// swiftlint:disable file_length
|
||||||
|
import Combine
|
||||||
import Kingfisher
|
import Kingfisher
|
||||||
import Mastodon
|
import Mastodon
|
||||||
import UIKit
|
import UIKit
|
||||||
|
@ -43,6 +44,7 @@ final class StatusView: UIView {
|
||||||
private let inReplyToView = UIView()
|
private let inReplyToView = UIView()
|
||||||
private let hasReplyFollowingView = UIView()
|
private let hasReplyFollowingView = UIView()
|
||||||
private var statusConfiguration: StatusContentConfiguration
|
private var statusConfiguration: StatusContentConfiguration
|
||||||
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
init(configuration: StatusContentConfiguration) {
|
init(configuration: StatusContentConfiguration) {
|
||||||
self.statusConfiguration = configuration
|
self.statusConfiguration = configuration
|
||||||
|
@ -327,6 +329,12 @@ private extension StatusView {
|
||||||
greaterThanOrEqualToConstant: .minimumButtonDimension / 2),
|
greaterThanOrEqualToConstant: .minimumButtonDimension / 2),
|
||||||
interactionsStackView.heightAnchor.constraint(greaterThanOrEqualToConstant: .minimumButtonDimension)
|
interactionsStackView.heightAnchor.constraint(greaterThanOrEqualToConstant: .minimumButtonDimension)
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
NotificationCenter.default.publisher(for: UIAccessibility.voiceOverStatusDidChangeNotification)
|
||||||
|
.sink { [weak self] _ in self?.configureUserInteractionEnabledForAccessibility() }
|
||||||
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyStatusConfiguration() {
|
func applyStatusConfiguration() {
|
||||||
|
@ -474,6 +482,18 @@ private extension StatusView {
|
||||||
shareButton.tag = viewModel.sharingURL?.hashValue ?? 0
|
shareButton.tag = viewModel.sharingURL?.hashValue ?? 0
|
||||||
|
|
||||||
menuButton.isEnabled = isAuthenticated
|
menuButton.isEnabled = isAuthenticated
|
||||||
|
|
||||||
|
isAccessibilityElement = !viewModel.configuration.isContextParent
|
||||||
|
|
||||||
|
let accessibilityAttributedLabel = NSMutableAttributedString(attributedString: mutableDisplayName)
|
||||||
|
|
||||||
|
if let bodyAccessibilityAttributedLabel = bodyView.accessibilityAttributedLabel {
|
||||||
|
accessibilityAttributedLabel.appendWithSeparator(bodyAccessibilityAttributedLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.accessibilityAttributedLabel = accessibilityAttributedLabel
|
||||||
|
|
||||||
|
configureUserInteractionEnabledForAccessibility()
|
||||||
}
|
}
|
||||||
// swiftlint:enable function_body_length
|
// swiftlint:enable function_body_length
|
||||||
|
|
||||||
|
@ -616,6 +636,11 @@ private extension StatusView {
|
||||||
|
|
||||||
statusConfiguration.viewModel.toggleFavorited()
|
statusConfiguration.viewModel.toggleFavorited()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func configureUserInteractionEnabledForAccessibility() {
|
||||||
|
isUserInteractionEnabled = !UIAccessibility.isVoiceOverRunning
|
||||||
|
|| statusConfiguration.viewModel.configuration.isContextParent
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension UIButton {
|
private extension UIButton {
|
||||||
|
|
|
@ -4,10 +4,10 @@ import Mastodon
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
final class PollResultView: UIView {
|
final class PollResultView: UIView {
|
||||||
|
let titleLabel = UILabel()
|
||||||
|
let percentLabel = UILabel()
|
||||||
private let verticalStackView = UIStackView()
|
private let verticalStackView = UIStackView()
|
||||||
private let horizontalStackView = UIStackView()
|
private let horizontalStackView = UIStackView()
|
||||||
private let titleLabel = UILabel()
|
|
||||||
private let percentLabel = UILabel()
|
|
||||||
private let percentView = UIProgressView()
|
private let percentView = UIProgressView()
|
||||||
|
|
||||||
init(option: Poll.Option, emojis: [Emoji], selected: Bool, multipleSelection: Bool, votersCount: Int) {
|
init(option: Poll.Option, emojis: [Emoji], selected: Bool, multipleSelection: Bool, votersCount: Int) {
|
||||||
|
|
|
@ -29,6 +29,9 @@ final class PollView: UIView {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let accessibilityAttributedLabel = NSMutableAttributedString(
|
||||||
|
string: NSLocalizedString("status.poll.accessibility-label", comment: ""))
|
||||||
|
|
||||||
if !viewModel.isPollExpired, !viewModel.hasVotedInPoll {
|
if !viewModel.isPollExpired, !viewModel.hasVotedInPoll {
|
||||||
for (index, option) in viewModel.pollOptions.enumerated() {
|
for (index, option) in viewModel.pollOptions.enumerated() {
|
||||||
let button = PollOptionButton(
|
let button = PollOptionButton(
|
||||||
|
@ -63,6 +66,41 @@ final class PollView: UIView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (index, view) in stackView.arrangedSubviews.enumerated() {
|
||||||
|
var title: NSAttributedString?
|
||||||
|
var percent: String?
|
||||||
|
let indexLabel = String.localizedStringWithFormat(
|
||||||
|
NSLocalizedString("status.poll.option-%ld", comment: ""),
|
||||||
|
index + 1)
|
||||||
|
|
||||||
|
if let optionView = view as? PollOptionButton,
|
||||||
|
let attributedTitle = optionView.attributedTitle(for: .normal) {
|
||||||
|
title = attributedTitle
|
||||||
|
|
||||||
|
let optionAccessibilityAttributedLabel = NSMutableAttributedString(string: indexLabel)
|
||||||
|
|
||||||
|
if viewModel.isPollMultipleSelection {
|
||||||
|
optionAccessibilityAttributedLabel.appendWithSeparator(
|
||||||
|
NSLocalizedString("compose.poll.accessibility.multiple-choices-allowed", comment: ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
optionAccessibilityAttributedLabel.appendWithSeparator(attributedTitle)
|
||||||
|
optionView.accessibilityAttributedLabel = optionAccessibilityAttributedLabel
|
||||||
|
} else if let resultView = view as? PollResultView {
|
||||||
|
title = resultView.titleLabel.attributedText
|
||||||
|
percent = resultView.percentLabel.text
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let presentTitle = title else { continue }
|
||||||
|
|
||||||
|
accessibilityAttributedLabel.appendWithSeparator(indexLabel)
|
||||||
|
accessibilityAttributedLabel.appendWithSeparator(presentTitle)
|
||||||
|
|
||||||
|
if let percent = percent {
|
||||||
|
accessibilityAttributedLabel.appendWithSeparator(percent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !viewModel.isPollExpired, !viewModel.hasVotedInPoll {
|
if !viewModel.isPollExpired, !viewModel.hasVotedInPoll {
|
||||||
stackView.addArrangedSubview(voteButton)
|
stackView.addArrangedSubview(voteButton)
|
||||||
|
|
||||||
|
@ -81,21 +119,43 @@ final class PollView: UIView {
|
||||||
|
|
||||||
stackView.addArrangedSubview(bottomStackView)
|
stackView.addArrangedSubview(bottomStackView)
|
||||||
|
|
||||||
votesCountLabel.text = String.localizedStringWithFormat(
|
let votesCount = String.localizedStringWithFormat(
|
||||||
NSLocalizedString("status.poll.participation-count", comment: ""),
|
NSLocalizedString("status.poll.participation-count", comment: ""),
|
||||||
viewModel.pollVotersCount)
|
viewModel.pollVotersCount)
|
||||||
|
|
||||||
|
votesCountLabel.text = votesCount
|
||||||
|
votesCountLabel.isAccessibilityElement = true
|
||||||
|
votesCountLabel.accessibilityLabel = votesCountLabel.text
|
||||||
|
accessibilityAttributedLabel.appendWithSeparator(votesCount)
|
||||||
|
|
||||||
if !viewModel.isPollExpired, let pollTimeLeft = viewModel.pollTimeLeft {
|
if !viewModel.isPollExpired, let pollTimeLeft = viewModel.pollTimeLeft {
|
||||||
expiryLabel.text = String.localizedStringWithFormat(
|
expiryLabel.text = String.localizedStringWithFormat(
|
||||||
NSLocalizedString("status.poll.time-left", comment: ""),
|
NSLocalizedString("status.poll.time-left", comment: ""),
|
||||||
pollTimeLeft)
|
pollTimeLeft)
|
||||||
refreshButton.isHidden = false
|
refreshButton.isHidden = false
|
||||||
|
accessibilityCustomActions =
|
||||||
|
[UIAccessibilityCustomAction(
|
||||||
|
name: NSLocalizedString("status.poll.refresh", comment: "")) { [weak self] _ in
|
||||||
|
self?.viewModel?.refreshPoll()
|
||||||
|
|
||||||
|
return true
|
||||||
|
}]
|
||||||
} else {
|
} else {
|
||||||
expiryLabel.text = NSLocalizedString("status.poll.closed", comment: "")
|
expiryLabel.text = NSLocalizedString("status.poll.closed", comment: "")
|
||||||
refreshButton.isHidden = true
|
refreshButton.isHidden = true
|
||||||
|
accessibilityCustomActions = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
expiryLabel.isAccessibilityElement = true
|
||||||
|
expiryLabel.accessibilityLabel = expiryLabel.text
|
||||||
|
|
||||||
|
if let expiry = expiryLabel.text {
|
||||||
|
accessibilityAttributedLabel.appendWithSeparator(expiry)
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshDividerLabel.isHidden = refreshButton.isHidden
|
refreshDividerLabel.isHidden = refreshButton.isHidden
|
||||||
|
|
||||||
|
self.accessibilityAttributedLabel = accessibilityAttributedLabel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,9 +52,45 @@ final class StatusBodyView: UIView {
|
||||||
|
|
||||||
pollView.isHidden = viewModel.pollOptions.isEmpty || !viewModel.shouldShowContent
|
pollView.isHidden = viewModel.pollOptions.isEmpty || !viewModel.shouldShowContent
|
||||||
pollView.viewModel = viewModel
|
pollView.viewModel = viewModel
|
||||||
|
pollView.isAccessibilityElement = !isContextParent || viewModel.hasVotedInPoll || viewModel.isPollExpired
|
||||||
|
|
||||||
cardView.viewModel = viewModel.cardViewModel
|
cardView.viewModel = viewModel.cardViewModel
|
||||||
cardView.isHidden = viewModel.cardViewModel == nil
|
cardView.isHidden = viewModel.cardViewModel == nil
|
||||||
|
|
||||||
|
let accessibilityAttributedLabel = NSMutableAttributedString(string: "")
|
||||||
|
|
||||||
|
if !spoilerTextLabel.isHidden {
|
||||||
|
accessibilityAttributedLabel.append(mutableSpoilerText)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !toggleShowContentButton.isHidden {
|
||||||
|
accessibilityAttributedLabel.appendWithSeparator(
|
||||||
|
NSLocalizedString("status.content-warning.accessibility", comment: ""))
|
||||||
|
|
||||||
|
if viewModel.shouldShowContent {
|
||||||
|
accessibilityAttributedLabel.appendWithSeparator(
|
||||||
|
NSLocalizedString("status.content-warning.accessibility.opened", comment: ""))
|
||||||
|
} else {
|
||||||
|
accessibilityAttributedLabel.appendWithSeparator(
|
||||||
|
NSLocalizedString("status.content-warning.accessibility.closed", comment: ""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !contentTextView.isHidden {
|
||||||
|
if spoilerTextLabel.isHidden {
|
||||||
|
accessibilityAttributedLabel.append(mutableContent)
|
||||||
|
} else {
|
||||||
|
accessibilityAttributedLabel.appendWithSeparator(mutableContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for view in [attachmentsView, pollView, cardView] where !view.isHidden {
|
||||||
|
guard let viewAccessibilityLabel = view.accessibilityLabel else { continue }
|
||||||
|
|
||||||
|
accessibilityAttributedLabel.appendWithSeparator(viewAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.accessibilityLabel = accessibilityAttributedLabel.string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ final class StatusTableViewCell: UITableViewCell {
|
||||||
guard let viewModel = viewModel else { return }
|
guard let viewModel = viewModel else { return }
|
||||||
|
|
||||||
contentConfiguration = StatusContentConfiguration(viewModel: viewModel).updated(for: state)
|
contentConfiguration = StatusContentConfiguration(viewModel: viewModel).updated(for: state)
|
||||||
|
accessibilityElements = [contentView]
|
||||||
}
|
}
|
||||||
|
|
||||||
override func layoutSubviews() {
|
override func layoutSubviews() {
|
||||||
|
|
Loading…
Reference in a new issue