feat: add accessibility supports for timeline

This commit is contained in:
CMK 2021-05-12 18:26:53 +08:00
parent 108c6af575
commit 6ba6598b96
18 changed files with 275 additions and 8 deletions

View File

@ -93,6 +93,22 @@
},
"time_left": "%s left",
"closed": "Closed"
},
"actions": {
"reply": "Reply",
"reblog": "Reblog",
"unreblog": "Unreblog",
"favorite": "Favorite",
"unfavorite": "Unfavorite",
"menu": "Menu"
},
"tag": {
"url": "URL",
"mention": "Mention",
"link": "Link",
"hashtag": "Hashtag",
"email": "Email",
"emoji": "Emoji"
}
},
"firendship": {
@ -125,6 +141,11 @@
"blocked_warning": "You cant view Artbots profile\n until they unblock you.",
"suspended_warning": "This account has been suspended.",
"user_suspended_warning": "%s's account has been suspended."
},
"accessibility": {
"count_replies": "%s replies",
"count_reblogs": "%s reblogs",
"count_favorites": "%s favorites"
}
}
},

View File

@ -756,6 +756,7 @@
DB02CDBE2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveUserInterfaceStyleBarButtonItem.swift; sourceTree = "<group>"; };
DB084B5625CBC56C00F898ED /* Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Status.swift; sourceTree = "<group>"; };
DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Instance.swift"; sourceTree = "<group>"; };
DB0F814C264BBDD900F2A12B /* ActiveLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ActiveLabel.swift; path = ../ActiveLabel.swift; sourceTree = "<group>"; };
DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightDimmableButton.swift; sourceTree = "<group>"; };
DB1D186B25EF5BA7003F1F23 /* PollTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollTableView.swift; sourceTree = "<group>"; };
@ -1645,6 +1646,7 @@
children = (
DBF53F5F25C14E88008AAC7B /* Mastodon.xctestplan */,
DBF53F6025C14E9D008AAC7B /* MastodonSDK.xctestplan */,
DB0F814C264BBDD900F2A12B /* ActiveLabel.swift */,
DB3D0FED25BAA42200EAA174 /* MastodonSDK */,
DB427DD425BAA00100D1B89D /* Mastodon */,
DB427DEB25BAA00100D1B89D /* MastodonTests */,

View File

@ -58,6 +58,7 @@ extension StatusSection {
)
}
cell.delegate = statusTableViewCellDelegate
cell.isAccessibilityElement = true
return cell
case .status(let objectID, let attribute),
.root(let objectID, let attribute),
@ -97,7 +98,22 @@ extension StatusSection {
}
}
cell.delegate = statusTableViewCellDelegate
switch item {
case .root:
cell.statusView.activeTextLabel.isAccessibilityElement = false
var accessibilityElements: [Any] = []
accessibilityElements.append(cell.statusView.nameLabel)
accessibilityElements.append(cell.statusView.dateLabel)
accessibilityElements.append(contentsOf: cell.statusView.activeTextLabel.createAccessibilityElements())
accessibilityElements.append(contentsOf: cell.statusView.statusMosaicImageViewContainer.imageViews)
accessibilityElements.append(cell.statusView.playerContainerView)
accessibilityElements.append(cell.statusView.actionToolbarContainer)
accessibilityElements.append(cell.threadMetaView)
cell.accessibilityElements = accessibilityElements
default:
cell.isAccessibilityElement = true
cell.accessibilityElements = nil
}
return cell
case .leafBottomLoader:
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ThreadReplyLoaderTableViewCell.self), for: indexPath) as! ThreadReplyLoaderTableViewCell
@ -179,6 +195,7 @@ extension StatusSection {
}()
cell.statusView.nameLabel.configure(content: nameText, emojiDict: (status.reblog ?? status).author.emojiDict)
cell.statusView.usernameLabel.text = "@" + (status.reblog ?? status).author.acct
// set avatar
if let reblog = status.reblog {
cell.statusView.avatarButton.isHidden = true
@ -196,6 +213,7 @@ extension StatusSection {
content: (status.reblog ?? status).content,
emojiDict: (status.reblog ?? status).emojiDict
)
cell.statusView.activeTextLabel.accessibilityLanguage = (status.reblog ?? status).language
// set visibility
if let visibility = (status.reblog ?? status).visibility {
@ -275,6 +293,7 @@ extension StatusSection {
break
}
}
imageView.accessibilityLabel = meta.altText
Publishers.CombineLatest(
statusItemAttribute.isImageLoaded,
statusItemAttribute.isRevealing
@ -452,6 +471,7 @@ extension StatusSection {
.sink { [weak cell] _ in
guard let cell = cell else { return }
cell.statusView.dateLabel.text = createdAt.shortTimeAgoSinceNow
cell.statusView.dateLabel.accessibilityLabel = createdAt.timeAgoSinceNow
}
.store(in: &cell.disposeBag)
@ -572,6 +592,7 @@ extension StatusSection {
formatter.timeStyle = .short
return formatter.string(from: status.createdAt)
}()
cell.threadMetaView.dateLabel.accessibilityLabel = DateFormatter.localizedString(from: status.createdAt, dateStyle: .medium, timeStyle: .short)
let reblogCountTitle: String = {
let count = status.reblogsCount.intValue
if count > 1 {
@ -609,6 +630,7 @@ extension StatusSection {
return L10n.Common.Controls.Status.userReblogged(name)
}()
cell.statusView.headerInfoLabel.configure(content: headerText, emojiDict: status.author.emojiDict)
cell.statusView.headerInfoLabel.isAccessibilityElement = true
} else if status.inReplyToID != nil {
cell.statusView.headerContainerView.isHidden = false
cell.statusView.headerIconLabel.attributedText = StatusView.iconAttributedString(image: StatusView.replyIconImage)
@ -621,8 +643,10 @@ extension StatusSection {
return L10n.Common.Controls.Status.userRepliedTo(name)
}()
cell.statusView.headerInfoLabel.configure(content: headerText, emojiDict: status.replyTo?.author.emojiDict ?? [:])
cell.statusView.headerInfoLabel.isAccessibilityElement = true
} else {
cell.statusView.headerContainerView.isHidden = true
cell.statusView.headerInfoLabel.isAccessibilityElement = false
}
}
@ -640,6 +664,9 @@ extension StatusSection {
return StatusSection.formattedNumberTitleForActionButton(count)
}()
cell.statusView.actionToolbarContainer.replyButton.setTitle(replyCountTitle, for: .normal)
cell.statusView.actionToolbarContainer.replyButton.accessibilityValue = status.repliesCount.flatMap {
L10n.Common.Controls.Timeline.Accessibility.countReplies($0.intValue)
} ?? nil
// set reblog
let isReblogged = status.rebloggedBy.flatMap { $0.contains(where: { $0.id == requestUserID }) } ?? false
let reblogCountTitle: String = {
@ -648,6 +675,11 @@ extension StatusSection {
}()
cell.statusView.actionToolbarContainer.reblogButton.setTitle(reblogCountTitle, for: .normal)
cell.statusView.actionToolbarContainer.isReblogButtonHighlight = isReblogged
cell.statusView.actionToolbarContainer.reblogButton.accessibilityLabel = isReblogged ? L10n.Common.Controls.Status.Actions.unreblog : L10n.Common.Controls.Status.Actions.reblog
cell.statusView.actionToolbarContainer.reblogButton.accessibilityValue = {
guard status.reblogsCount.intValue > 0 else { return nil }
return L10n.Common.Controls.Timeline.Accessibility.countReblogs(status.reblogsCount.intValue)
}()
// set like
let isLike = status.favouritedBy.flatMap { $0.contains(where: { $0.id == requestUserID }) } ?? false
let favoriteCountTitle: String = {
@ -656,7 +688,11 @@ extension StatusSection {
}()
cell.statusView.actionToolbarContainer.favoriteButton.setTitle(favoriteCountTitle, for: .normal)
cell.statusView.actionToolbarContainer.isFavoriteButtonHighlight = isLike
cell.statusView.actionToolbarContainer.favoriteButton.accessibilityLabel = isLike ? L10n.Common.Controls.Status.Actions.unfavorite : L10n.Common.Controls.Status.Actions.favorite
cell.statusView.actionToolbarContainer.favoriteButton.accessibilityValue = {
guard status.favouritesCount.intValue > 0 else { return nil }
return L10n.Common.Controls.Timeline.Accessibility.countReblogs(status.favouritesCount.intValue)
}()
Publishers.CombineLatest(
dependency.context.blockDomainService.blockedDomains,
ManagedObjectObserver.observe(object: status.authorForUserProvider)

View File

@ -32,6 +32,8 @@ extension ActiveLabel {
text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
#endif
accessibilityContainerType = .semanticGroup
switch style {
case .default:
font = .preferredFont(forTextStyle: .body)
@ -61,8 +63,10 @@ extension ActiveLabel {
if let parseResult = try? MastodonStatusContent.parse(content: content, emojiDict: emojiDict) {
text = parseResult.trimmed
activeEntities = parseResult.activeEntities
accessibilityLabel = parseResult.original
} else {
text = ""
accessibilityLabel = nil
}
}
@ -79,5 +83,110 @@ extension ActiveLabel {
let parseResult = MastodonField.parse(field: field)
text = parseResult.value
activeEntities = parseResult.activeEntities
accessibilityLabel = parseResult.value
}
}
extension ActiveEntity {
var accessibilityLabelDescription: String {
switch self.type {
case .email: return L10n.Common.Controls.Status.Tag.email
case .hashtag: return L10n.Common.Controls.Status.Tag.hashtag
case .mention: return L10n.Common.Controls.Status.Tag.mention
case .url: return L10n.Common.Controls.Status.Tag.url
case .emoji: return L10n.Common.Controls.Status.Tag.emoji
}
}
var accessibilityValueDescription: String {
switch self.type {
case .email(let text, _): return text
case .hashtag(let text, _): return text
case .mention(let text, _): return text
case .url(_, let trimmed, _, _): return trimmed
case .emoji(let text, _, _): return text
}
}
func accessibilityElement(in accessibilityContainer: Any) -> ActiveLabelAccessibilityElement? {
if case .emoji = self.type {
return nil
}
let element = ActiveLabelAccessibilityElement(accessibilityContainer: accessibilityContainer)
element.accessibilityTraits = .button
element.accessibilityLabel = accessibilityLabelDescription
element.accessibilityValue = accessibilityValueDescription
return element
}
}
final class ActiveLabelAccessibilityElement: UIAccessibilityElement {
var index: Int!
}
// MARK: - UIAccessibilityContainer
extension ActiveLabel {
func createAccessibilityElements() -> [UIAccessibilityElement] {
var elements: [UIAccessibilityElement] = []
let element = ActiveLabelAccessibilityElement(accessibilityContainer: self)
element.accessibilityTraits = .staticText
element.accessibilityLabel = accessibilityLabel
element.accessibilityFrame = superview!.convert(frame, to: nil)
element.accessibilityLanguage = accessibilityLanguage
elements.append(element)
for eneity in activeEntities {
guard let element = eneity.accessibilityElement(in: self) else { continue }
var glyphRange = NSRange()
layoutManager.characterRange(forGlyphRange: eneity.range, actualGlyphRange: &glyphRange)
let rect = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
element.accessibilityFrame = self.convert(rect, to: nil)
element.accessibilityContainer = self
elements.append(element)
}
return elements
}
// public override func accessibilityElementCount() -> Int {
// return 1 + activeEntities.count
// }
//
// public override func accessibilityElement(at index: Int) -> Any? {
// if index == 0 {
// let element = ActiveLabelAccessibilityElement(accessibilityContainer: self)
// element.accessibilityTraits = .staticText
// element.accessibilityLabel = accessibilityLabel
// element.accessibilityFrame = superview!.convert(frame, to: nil)
// element.index = index
// return element
// }
//
// let index = index - 1
// guard index < activeEntities.count else { return nil }
// let eneity = activeEntities[index]
// guard let element = eneity.accessibilityElement(in: self) else { return nil }
//
// var glyphRange = NSRange()
// layoutManager.characterRange(forGlyphRange: eneity.range, actualGlyphRange: &glyphRange)
// let rect = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
// element.accessibilityFrame = self.convert(rect, to: nil)
// element.accessibilityContainer = self
//
// return element
// }
//
// public override func index(ofAccessibilityElement element: Any) -> Int {
// guard let element = element as? ActiveLabelAccessibilityElement,
// let index = element.index else {
// return NSNotFound
// }
//
// return index
// }
}

View File

@ -208,6 +208,20 @@ internal enum L10n {
internal static func userRepliedTo(_ p1: Any) -> String {
return L10n.tr("Localizable", "Common.Controls.Status.UserRepliedTo", String(describing: p1))
}
internal enum Actions {
/// Favorite
internal static let favorite = L10n.tr("Localizable", "Common.Controls.Status.Actions.Favorite")
/// Menu
internal static let menu = L10n.tr("Localizable", "Common.Controls.Status.Actions.Menu")
/// Reblog
internal static let reblog = L10n.tr("Localizable", "Common.Controls.Status.Actions.Reblog")
/// Reply
internal static let reply = L10n.tr("Localizable", "Common.Controls.Status.Actions.Reply")
/// Unfavorite
internal static let unfavorite = L10n.tr("Localizable", "Common.Controls.Status.Actions.Unfavorite")
/// Unreblog
internal static let unreblog = L10n.tr("Localizable", "Common.Controls.Status.Actions.Unreblog")
}
internal enum Poll {
/// Closed
internal static let closed = L10n.tr("Localizable", "Common.Controls.Status.Poll.Closed")
@ -238,8 +252,36 @@ internal enum L10n {
}
}
}
internal enum Tag {
/// Email
internal static let email = L10n.tr("Localizable", "Common.Controls.Status.Tag.Email")
/// Emoji
internal static let emoji = L10n.tr("Localizable", "Common.Controls.Status.Tag.Emoji")
/// Hashtag
internal static let hashtag = L10n.tr("Localizable", "Common.Controls.Status.Tag.Hashtag")
/// Link
internal static let link = L10n.tr("Localizable", "Common.Controls.Status.Tag.Link")
/// Mention
internal static let mention = L10n.tr("Localizable", "Common.Controls.Status.Tag.Mention")
/// URL
internal static let url = L10n.tr("Localizable", "Common.Controls.Status.Tag.Url")
}
}
internal enum Timeline {
internal enum Accessibility {
/// %@ favorites
internal static func countFavorites(_ p1: Any) -> String {
return L10n.tr("Localizable", "Common.Controls.Timeline.Accessibility.CountFavorites", String(describing: p1))
}
/// %@ reblogs
internal static func countReblogs(_ p1: Any) -> String {
return L10n.tr("Localizable", "Common.Controls.Timeline.Accessibility.CountReblogs", String(describing: p1))
}
/// %@ replies
internal static func countReplies(_ p1: Any) -> String {
return L10n.tr("Localizable", "Common.Controls.Timeline.Accessibility.CountReplies", String(describing: p1))
}
}
internal enum Header {
/// You cant view Artbots profile\n until they unblock you.
internal static let blockedWarning = L10n.tr("Localizable", "Common.Controls.Timeline.Header.BlockedWarning")

View File

@ -59,7 +59,6 @@ extension StatusTableViewCellDelegate where Self: StatusProvider {
extension StatusTableViewCellDelegate where Self: StatusProvider {
func statusTableViewCell(_ cell: StatusTableViewCell, mosaicImageViewContainer: MosaicImageViewContainer, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView) {
StatusProviderFacade.responseToStatusContentWarningRevealAction(provider: self, cell: cell)
}
@ -76,7 +75,11 @@ extension StatusTableViewCellDelegate where Self: StatusProvider {
extension StatusTableViewCellDelegate where Self: StatusProvider & MediaPreviewableViewController {
func statusTableViewCell(_ cell: StatusTableViewCell, mosaicImageViewContainer: MosaicImageViewContainer, didTapImageView imageView: UIImageView, atIndex index: Int) {
StatusProviderFacade.coordinateToStatusMediaPreviewScene(provider: self, cell: cell, mosaicImageView: mosaicImageViewContainer, didTapImageView: imageView, atIndex: index)
if UIAccessibility.isVoiceOverRunning, !(self is ThreadViewController) {
StatusProviderFacade.coordinateToStatusThreadScene(for: .primary, provider: self, cell: cell)
} else {
StatusProviderFacade.coordinateToStatusMediaPreviewScene(provider: self, cell: cell, mosaicImageView: mosaicImageViewContainer, didTapImageView: imageView, atIndex: index)
}
}
}

View File

@ -64,6 +64,12 @@ Please check your internet connection.";
"Common.Controls.Firendship.UnblockUser" = "Unblock %@";
"Common.Controls.Firendship.Unmute" = "Unmute";
"Common.Controls.Firendship.UnmuteUser" = "Unmute %@";
"Common.Controls.Status.Actions.Favorite" = "Favorite";
"Common.Controls.Status.Actions.Menu" = "Menu";
"Common.Controls.Status.Actions.Reblog" = "Reblog";
"Common.Controls.Status.Actions.Reply" = "Reply";
"Common.Controls.Status.Actions.Unfavorite" = "Unfavorite";
"Common.Controls.Status.Actions.Unreblog" = "Unreblog";
"Common.Controls.Status.ContentWarning" = "content warning";
"Common.Controls.Status.ContentWarningText" = "cw: %@";
"Common.Controls.Status.MediaContentWarning" = "Tap to reveal that may be sensitive";
@ -75,8 +81,17 @@ Please check your internet connection.";
"Common.Controls.Status.Poll.VoterCount.Multiple" = "%d voters";
"Common.Controls.Status.Poll.VoterCount.Single" = "%d voter";
"Common.Controls.Status.ShowPost" = "Show Post";
"Common.Controls.Status.Tag.Email" = "Email";
"Common.Controls.Status.Tag.Emoji" = "Emoji";
"Common.Controls.Status.Tag.Hashtag" = "Hashtag";
"Common.Controls.Status.Tag.Link" = "Link";
"Common.Controls.Status.Tag.Mention" = "Mention";
"Common.Controls.Status.Tag.Url" = "URL";
"Common.Controls.Status.UserReblogged" = "%@ reblogged";
"Common.Controls.Status.UserRepliedTo" = "Replied to %@";
"Common.Controls.Timeline.Accessibility.CountFavorites" = "%@ favorites";
"Common.Controls.Timeline.Accessibility.CountReblogs" = "%@ reblogs";
"Common.Controls.Timeline.Accessibility.CountReplies" = "%@ replies";
"Common.Controls.Timeline.Header.BlockedWarning" = "You cant view Artbots profile
until they unblock you.";
"Common.Controls.Timeline.Header.BlockingWarning" = "You cant view Artbots profile

View File

@ -36,7 +36,7 @@ final class MediaPreviewViewModel: NSObject {
switch entity.type {
case .image:
guard let url = URL(string: entity.url) else { continue }
let meta = MediaPreviewImageViewModel.RemoteImagePreviewMeta(url: url, thumbnail: thumbnail)
let meta = MediaPreviewImageViewModel.RemoteImagePreviewMeta(url: url, thumbnail: thumbnail, altText: entity.descriptionString)
let mediaPreviewImageModel = MediaPreviewImageViewModel(meta: meta)
let mediaPreviewImageViewController = MediaPreviewImageViewController()
mediaPreviewImageViewController.viewModel = mediaPreviewImageModel
@ -60,7 +60,7 @@ final class MediaPreviewViewModel: NSObject {
managedObjectContext.performAndWait {
let account = managedObjectContext.object(with: meta.accountObjectID) as! MastodonUser
let avatarURL = account.headerImageURLWithFallback(domain: account.domain)
let meta = MediaPreviewImageViewModel.RemoteImagePreviewMeta(url: avatarURL, thumbnail: meta.preloadThumbnailImage)
let meta = MediaPreviewImageViewModel.RemoteImagePreviewMeta(url: avatarURL, thumbnail: meta.preloadThumbnailImage, altText: nil)
let mediaPreviewImageModel = MediaPreviewImageViewModel(meta: meta)
let mediaPreviewImageViewController = MediaPreviewImageViewController()
mediaPreviewImageViewController.viewModel = mediaPreviewImageModel
@ -80,7 +80,7 @@ final class MediaPreviewViewModel: NSObject {
managedObjectContext.performAndWait {
let account = managedObjectContext.object(with: meta.accountObjectID) as! MastodonUser
let avatarURL = account.avatarImageURLWithFallback(domain: account.domain)
let meta = MediaPreviewImageViewModel.RemoteImagePreviewMeta(url: avatarURL, thumbnail: meta.preloadThumbnailImage)
let meta = MediaPreviewImageViewModel.RemoteImagePreviewMeta(url: avatarURL, thumbnail: meta.preloadThumbnailImage, altText: nil)
let mediaPreviewImageModel = MediaPreviewImageViewModel(meta: meta)
let mediaPreviewImageViewController = MediaPreviewImageViewController()
mediaPreviewImageViewController.viewModel = mediaPreviewImageModel

View File

@ -18,6 +18,7 @@ final class MediaPreviewImageView: UIScrollView {
imageView.isUserInteractionEnabled = true
// accessibility
imageView.accessibilityIgnoresInvertColors = true
imageView.isAccessibilityElement = true
return imageView
}()

View File

@ -76,6 +76,7 @@ extension MediaPreviewImageViewController {
guard let image = image else { return }
self.previewImageView.imageView.image = image
self.previewImageView.setup(image: image, container: self.previewImageView, forceUpdate: true)
self.previewImageView.imageView.accessibilityLabel = self.viewModel.altText
}
.store(in: &disposeBag)
}

View File

@ -17,10 +17,12 @@ class MediaPreviewImageViewModel {
// output
let image: CurrentValueSubject<UIImage?, Never>
let altText: String?
init(meta: RemoteImagePreviewMeta) {
self.item = .status(meta)
self.image = CurrentValueSubject(meta.thumbnail)
self.altText = meta.altText
let url = meta.url
ImageDownloader.default.download(URLRequest(url: url), completion: { [weak self] response in
@ -38,6 +40,7 @@ class MediaPreviewImageViewModel {
init(meta: LocalImagePreviewMeta) {
self.item = .local(meta)
self.image = CurrentValueSubject(meta.image)
self.altText = nil
}
}
@ -64,6 +67,7 @@ extension MediaPreviewImageViewModel {
struct RemoteImagePreviewMeta {
let url: URL
let thumbnail: UIImage?
let altText: String?
}
struct LocalImagePreviewMeta {

View File

@ -31,6 +31,7 @@ final class MosaicImageViewContainer: UIView {
let tapGesture = UITapGestureRecognizer.singleTapGestureRecognizer
tapGesture.addTarget(self, action: #selector(MosaicImageViewContainer.photoTapGestureRecognizerHandler(_:)))
imageView.addGestureRecognizer(tapGesture)
imageView.isAccessibilityElement = true
}
}
}

View File

@ -26,6 +26,7 @@ class ContentWarningOverlayView: UIView {
label.text = L10n.Common.Controls.Status.mediaContentWarning
label.textAlignment = .center
label.numberOfLines = 0
label.isAccessibilityElement = false
return label
}()
@ -40,6 +41,7 @@ class ContentWarningOverlayView: UIView {
label.text = L10n.Common.Controls.Status.mediaContentWarning
label.textColor = Asset.Colors.Label.primary.color
label.textAlignment = .center
label.isAccessibilityElement = false
return label
}()
let blurContentWarningLabel: UILabel = {
@ -49,6 +51,7 @@ class ContentWarningOverlayView: UIView {
label.textColor = Asset.Colors.Label.secondary.color
label.textAlignment = .center
label.layer.setupShadow()
label.isAccessibilityElement = false
return label
}()

View File

@ -96,6 +96,7 @@ final class StatusView: UIView {
label.textColor = Asset.Colors.Label.secondary.color
label.font = .systemFont(ofSize: 17)
label.text = "·"
label.isAccessibilityElement = false
return label
}()
@ -104,6 +105,7 @@ final class StatusView: UIView {
label.font = .systemFont(ofSize: 15, weight: .regular)
label.textColor = Asset.Colors.Label.secondary.color
label.text = "@alice"
label.isAccessibilityElement = false
return label
}()

View File

@ -79,6 +79,10 @@ extension ThreadMetaView {
favoriteButton.setContentHuggingPriority(.required - 1, for: .horizontal)
updateContainerLayout()
// TODO:
reblogButton.isAccessibilityElement = false
favoriteButton.isAccessibilityElement = false
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {

View File

@ -81,6 +81,7 @@ final class StatusTableViewCell: UITableViewCell, StatusCell {
threadMetaView.isHidden = true
disposeBag.removeAll()
observations.removeAll()
isAccessibilityElement = false // reset behavior
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
@ -357,3 +358,10 @@ extension StatusTableViewCell: ActionToolbarContainerDelegate {
}
}
extension StatusTableViewCell {
override var accessibilityActivationPoint: CGPoint {
get { return .zero }
set { }
}
}

View File

@ -105,6 +105,11 @@ extension ActionToolbarContainer {
let starImage = UIImage(systemName: "star.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 17, weight: .bold))!.withRenderingMode(.alwaysTemplate)
let moreImage = UIImage(systemName: "ellipsis", withConfiguration: UIImage.SymbolConfiguration(pointSize: 17, weight: .bold))!.withRenderingMode(.alwaysTemplate)
replyButton.accessibilityLabel = L10n.Common.Controls.Status.Actions.reply
reblogButton.accessibilityLabel = L10n.Common.Controls.Status.Actions.reblog // needs update to follow state
favoriteButton.accessibilityLabel = L10n.Common.Controls.Status.Actions.favorite // needs update to follow state
moreButton.accessibilityLabel = L10n.Common.Controls.Status.Actions.menu
switch style {
case .inline:
buttons.forEach { button in
@ -194,6 +199,14 @@ extension ActionToolbarContainer {
}
extension ActionToolbarContainer {
override var accessibilityElements: [Any]? {
get { [replyButton, reblogButton, favoriteButton, moreButton] }
set { }
}
}
#if DEBUG
import SwiftUI

View File

@ -28,7 +28,8 @@ struct MosaicImageViewModel {
let mosaicMeta = MosaicMeta(
url: url,
size: CGSize(width: width, height: height),
blurhash: element.blurhash
blurhash: element.blurhash,
altText: element.descriptionString
)
metas.append(mosaicMeta)
}
@ -43,6 +44,7 @@ struct MosaicMeta {
let url: URL
let size: CGSize
let blurhash: String?
let altText: String?
let workingQueue = DispatchQueue(label: "org.joinmastodon.Mastodon.MosaicMeta.working-queue", qos: .userInitiated, attributes: .concurrent)