From a3a4af182dfb6e648f9a0e6f3b3c9fd4b170afbf Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Thu, 27 Aug 2020 15:23:21 -0700 Subject: [PATCH] Improves AppBannerView accessibility --- AltStore/App Detail/AppViewController.swift | 6 +- AltStore/App IDs/AppIDsViewController.swift | 28 ++++-- AltStore/Base.lproj/Main.storyboard | 3 - AltStore/Browse/BrowseViewController.swift | 13 +-- AltStore/Components/AppBannerView.swift | 93 ++++++++++++++++++++ AltStore/Components/AppBannerView.xib | 35 +++++--- AltStore/Components/PillButton.swift | 9 ++ AltStore/My Apps/MyAppsViewController.swift | 47 ++++++---- AltStore/News/NewsViewController.swift | 12 ++- AltStore/Sources/SourcesViewController.swift | 5 +- 10 files changed, 201 insertions(+), 50 deletions(-) diff --git a/AltStore/App Detail/AppViewController.swift b/AltStore/App Detail/AppViewController.swift index c9c04f99..470e01ab 100644 --- a/AltStore/App Detail/AppViewController.swift +++ b/AltStore/App Detail/AppViewController.swift @@ -81,14 +81,14 @@ class AppViewController: UIViewController self.bannerView.frame = CGRect(x: 0, y: 0, width: 300, height: 93) self.bannerView.backgroundEffectView.effect = UIBlurEffect(style: .regular) self.bannerView.backgroundEffectView.backgroundColor = .clear - self.bannerView.titleLabel.text = self.app.name - self.bannerView.subtitleLabel.text = self.app.developerName self.bannerView.iconImageView.image = nil self.bannerView.iconImageView.tintColor = self.app.tintColor self.bannerView.button.tintColor = self.app.tintColor - self.bannerView.betaBadgeView.isHidden = !self.app.isBeta self.bannerView.tintColor = self.app.tintColor + self.bannerView.configure(for: self.app) + self.bannerView.accessibilityTraits.remove(.button) + self.bannerView.button.addTarget(self, action: #selector(AppViewController.performAppAction(_:)), for: .primaryActionTriggered) self.backButtonContainerView.tintColor = self.app.tintColor diff --git a/AltStore/App IDs/AppIDsViewController.swift b/AltStore/App IDs/AppIDsViewController.swift index 49d012e8..58d10750 100644 --- a/AltStore/App IDs/AppIDsViewController.swift +++ b/AltStore/App IDs/AppIDsViewController.swift @@ -78,10 +78,11 @@ private extension AppIDsViewController cell.bannerView.iconImageView.isHidden = true cell.bannerView.button.isIndicatingActivity = false - cell.bannerView.betaBadgeView.isHidden = true cell.bannerView.buttonLabel.text = NSLocalizedString("Expires in", comment: "") + let attributedAccessibilityLabel = NSMutableAttributedString(string: appID.name + ". ") + if let expirationDate = appID.expirationDate { cell.bannerView.button.isHidden = false @@ -92,19 +93,16 @@ private extension AppIDsViewController let currentDate = Date() let numberOfDays = expirationDate.numberOfCalendarDays(since: currentDate) + let numberOfDaysText = (numberOfDays == 1) ? NSLocalizedString("1 day", comment: "") : String(format: NSLocalizedString("%@ days", comment: ""), NSNumber(value: numberOfDays)) + cell.bannerView.button.setTitle(numberOfDaysText.uppercased(), for: .normal) - if numberOfDays == 1 - { - cell.bannerView.button.setTitle(NSLocalizedString("1 DAY", comment: ""), for: .normal) - } - else - { - cell.bannerView.button.setTitle(String(format: NSLocalizedString("%@ DAYS", comment: ""), NSNumber(value: numberOfDays)), for: .normal) - } + attributedAccessibilityLabel.mutableString.append(String(format: NSLocalizedString("Expires in %@.", comment: ""), numberOfDaysText) + " ") } else { cell.bannerView.button.isHidden = true + cell.bannerView.button.isUserInteractionEnabled = true + cell.bannerView.buttonLabel.isHidden = true } @@ -112,6 +110,18 @@ private extension AppIDsViewController cell.bannerView.subtitleLabel.text = appID.bundleIdentifier cell.bannerView.subtitleLabel.numberOfLines = 2 + let attributedBundleIdentifier = NSMutableAttributedString(string: appID.bundleIdentifier.lowercased(), attributes: [.accessibilitySpeechPunctuation: true]) + + if let team = appID.team, let range = attributedBundleIdentifier.string.range(of: team.identifier.lowercased()), #available(iOS 13, *) + { + // Prefer to speak the team ID one character at a time. + let nsRange = NSRange(range, in: attributedBundleIdentifier.string) + attributedBundleIdentifier.addAttributes([.accessibilitySpeechSpellOut: true], range: nsRange) + } + + attributedAccessibilityLabel.append(attributedBundleIdentifier) + cell.bannerView.accessibilityAttributedLabel = attributedAccessibilityLabel + // Make sure refresh button is correct size. cell.layoutIfNeeded() } diff --git a/AltStore/Base.lproj/Main.storyboard b/AltStore/Base.lproj/Main.storyboard index 79a6a15b..9b8ee1fc 100644 --- a/AltStore/Base.lproj/Main.storyboard +++ b/AltStore/Base.lproj/Main.storyboard @@ -648,9 +648,6 @@ World - - - diff --git a/AltStore/Browse/BrowseViewController.swift b/AltStore/Browse/BrowseViewController.swift index d104b261..50d4a493 100644 --- a/AltStore/Browse/BrowseViewController.swift +++ b/AltStore/Browse/BrowseViewController.swift @@ -83,11 +83,9 @@ private extension BrowseViewController cell.layoutMargins.left = self.view.layoutMargins.left cell.layoutMargins.right = self.view.layoutMargins.right - cell.subtitleLabel.text = app.subtitle cell.imageURLs = Array(app.screenshotURLs.prefix(2)) - cell.bannerView.titleLabel.text = app.name - cell.bannerView.subtitleLabel.text = app.developerName - cell.bannerView.betaBadgeView.isHidden = !app.isBeta + + cell.bannerView.configure(for: app) cell.bannerView.iconImageView.image = nil cell.bannerView.iconImageView.isIndicatingActivity = true @@ -104,7 +102,10 @@ private extension BrowseViewController if app.installedApp == nil { - cell.bannerView.button.setTitle(NSLocalizedString("FREE", comment: ""), for: .normal) + let buttonTitle = NSLocalizedString("Free", comment: "") + cell.bannerView.button.setTitle(buttonTitle.uppercased(), for: .normal) + cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Download %@", comment: ""), app.name) + cell.bannerView.button.accessibilityValue = buttonTitle let progress = AppManager.shared.installationProgress(for: app) cell.bannerView.button.progress = progress @@ -121,6 +122,8 @@ private extension BrowseViewController else { cell.bannerView.button.setTitle(NSLocalizedString("OPEN", comment: ""), for: .normal) + cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Open %@", comment: ""), app.name) + cell.bannerView.button.accessibilityValue = nil cell.bannerView.button.progress = nil cell.bannerView.button.countdownDate = nil } diff --git a/AltStore/Components/AppBannerView.swift b/AltStore/Components/AppBannerView.swift index 4e9c6802..622bb2f1 100644 --- a/AltStore/Components/AppBannerView.swift +++ b/AltStore/Components/AppBannerView.swift @@ -11,6 +11,31 @@ import Roxas class AppBannerView: RSTNibView { + override var accessibilityLabel: String? { + get { return self.accessibilityView?.accessibilityLabel } + set { self.accessibilityView?.accessibilityLabel = newValue } + } + + override open var accessibilityAttributedLabel: NSAttributedString? { + get { return self.accessibilityView?.accessibilityAttributedLabel } + set { self.accessibilityView?.accessibilityAttributedLabel = newValue } + } + + override var accessibilityValue: String? { + get { return self.accessibilityView?.accessibilityValue } + set { self.accessibilityView?.accessibilityValue = newValue } + } + + override open var accessibilityAttributedValue: NSAttributedString? { + get { return self.accessibilityView?.accessibilityAttributedValue } + set { self.accessibilityView?.accessibilityAttributedValue = newValue } + } + + override open var accessibilityTraits: UIAccessibilityTraits { + get { return self.accessibilityView?.accessibilityTraits ?? [] } + set { self.accessibilityView?.accessibilityTraits = newValue } + } + private var originalTintColor: UIColor? @IBOutlet var titleLabel: UILabel! @@ -21,7 +46,33 @@ class AppBannerView: RSTNibView @IBOutlet var betaBadgeView: UIView! @IBOutlet var backgroundEffectView: UIVisualEffectView! + @IBOutlet private var vibrancyView: UIVisualEffectView! + @IBOutlet private var accessibilityView: UIView! + + override init(frame: CGRect) + { + super.init(frame: frame) + + self.initialize() + } + + required init?(coder: NSCoder) + { + super.init(coder: coder) + + self.initialize() + } + + private func initialize() + { + self.accessibilityView.accessibilityTraits.formUnion(.button) + + self.isAccessibilityElement = false + self.accessibilityElements = [self.accessibilityView, self.button].compactMap { $0 } + + self.betaBadgeView.isHidden = true + } override func tintColorDidChange() { @@ -36,6 +87,48 @@ class AppBannerView: RSTNibView } } +extension AppBannerView +{ + func configure(for app: AppProtocol) + { + struct AppValues + { + var name: String + var developerName: String? = nil + var isBeta: Bool = false + + init(app: AppProtocol) + { + self.name = app.name + + guard let storeApp = (app as? StoreApp) ?? (app as? InstalledApp)?.storeApp else { return } + self.developerName = storeApp.developerName + + if storeApp.isBeta + { + self.name = String(format: NSLocalizedString("%@ beta", comment: ""), app.name) + self.isBeta = true + } + } + } + + let values = AppValues(app: app) + self.titleLabel.text = app.name // Don't use values.name since that already includes "beta". + self.betaBadgeView.isHidden = !values.isBeta + + if let developerName = values.developerName + { + self.subtitleLabel.text = developerName + self.accessibilityLabel = String(format: NSLocalizedString("%@ by %@", comment: ""), values.name, developerName) + } + else + { + self.subtitleLabel.text = NSLocalizedString("Sideloaded", comment: "") + self.accessibilityLabel = values.name + } + } +} + private extension AppBannerView { func update() diff --git a/AltStore/Components/AppBannerView.xib b/AltStore/Components/AppBannerView.xib index 7c6634f6..95337f61 100644 --- a/AltStore/Components/AppBannerView.xib +++ b/AltStore/Components/AppBannerView.xib @@ -1,15 +1,17 @@ - + - + + + @@ -25,6 +27,13 @@ + + + + + + + @@ -45,20 +54,20 @@ - + - + - + @@ -67,14 +76,13 @@ - +