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 @@
-
+
+
+
+
+
@@ -137,5 +149,8 @@
+
+
+
diff --git a/AltStore/Components/PillButton.swift b/AltStore/Components/PillButton.swift
index c9483ee8..10c4c0c4 100644
--- a/AltStore/Components/PillButton.swift
+++ b/AltStore/Components/PillButton.swift
@@ -10,6 +10,14 @@ import UIKit
class PillButton: UIButton
{
+ override var accessibilityValue: String? {
+ get {
+ guard self.progress != nil else { return super.accessibilityValue }
+ return self.progressView.accessibilityValue
+ }
+ set { super.accessibilityValue = newValue }
+ }
+
var progress: Progress? {
didSet {
self.progressView.progress = Float(self.progress?.fractionCompleted ?? 0)
@@ -78,6 +86,7 @@ class PillButton: UIButton
super.awakeFromNib()
self.layer.masksToBounds = true
+ self.accessibilityTraits.formUnion([.updatesFrequently, .button])
self.activityIndicatorView.style = .white
self.activityIndicatorView.isUserInteractionEnabled = false
diff --git a/AltStore/My Apps/MyAppsViewController.swift b/AltStore/My Apps/MyAppsViewController.swift
index a646a31b..8575f160 100644
--- a/AltStore/My Apps/MyAppsViewController.swift
+++ b/AltStore/My Apps/MyAppsViewController.swift
@@ -201,13 +201,30 @@ private extension MyAppsViewController
cell.tintColor = app.tintColor ?? .altPrimary
cell.versionDescriptionTextView.text = app.versionDescription
- cell.bannerView.titleLabel.text = app.name
cell.bannerView.iconImageView.image = nil
cell.bannerView.iconImageView.isIndicatingActivity = true
- cell.bannerView.betaBadgeView.isHidden = !app.isBeta
+
+ cell.bannerView.configure(for: app)
+
+ let versionDate = Date().relativeDateString(since: app.versionDate, dateFormatter: self.dateFormatter)
+ cell.bannerView.subtitleLabel.text = versionDate
+
+ let appName: String
+
+ if app.isBeta
+ {
+ appName = String(format: NSLocalizedString("%@ beta", comment: ""), app.name)
+ }
+ else
+ {
+ appName = app.name
+ }
+
+ cell.bannerView.accessibilityLabel = String(format: NSLocalizedString("%@ %@ update. Released on %@.", comment: ""), appName, app.version, versionDate)
cell.bannerView.button.isIndicatingActivity = false
cell.bannerView.button.addTarget(self, action: #selector(MyAppsViewController.updateApp(_:)), for: .primaryActionTriggered)
+ cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Update %@", comment: ""), installedApp.name)
if self.expandedAppUpdates.contains(app.bundleIdentifier)
{
@@ -223,8 +240,6 @@ private extension MyAppsViewController
let progress = AppManager.shared.installationProgress(for: app)
cell.bannerView.button.progress = progress
- cell.bannerView.subtitleLabel.text = Date().relativeDateString(since: app.versionDate, dateFormatter: self.dateFormatter)
-
cell.setNeedsLayout()
}
dataSource.prefetchHandler = { (installedApp, indexPath, completionHandler) in
@@ -295,8 +310,9 @@ private extension MyAppsViewController
cell.deactivateBadge?.transform = CGAffineTransform.identity.scaledBy(x: 0.33, y: 0.33)
}
+ cell.bannerView.configure(for: installedApp)
+
cell.bannerView.iconImageView.isIndicatingActivity = true
- cell.bannerView.betaBadgeView.isHidden = !(installedApp.storeApp?.isBeta ?? false)
cell.bannerView.buttonLabel.isHidden = false
cell.bannerView.buttonLabel.text = NSLocalizedString("Expires in", comment: "")
@@ -308,18 +324,21 @@ private extension MyAppsViewController
let currentDate = Date()
let numberOfDays = installedApp.expirationDate.numberOfCalendarDays(since: currentDate)
+ let numberOfDaysText: String
if numberOfDays == 1
{
- cell.bannerView.button.setTitle(NSLocalizedString("1 DAY", comment: ""), for: .normal)
+ numberOfDaysText = NSLocalizedString("1 day", comment: "")
}
else
{
- cell.bannerView.button.setTitle(String(format: NSLocalizedString("%@ DAYS", comment: ""), NSNumber(value: numberOfDays)), for: .normal)
+ numberOfDaysText = String(format: NSLocalizedString("%@ days", comment: ""), NSNumber(value: numberOfDays))
}
-
- cell.bannerView.titleLabel.text = installedApp.name
- cell.bannerView.subtitleLabel.text = installedApp.storeApp?.developerName ?? NSLocalizedString("Sideloaded", comment: "")
+
+ cell.bannerView.button.setTitle(numberOfDaysText.uppercased(), for: .normal)
+ cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Refresh %@", comment: ""), installedApp.name)
+
+ cell.bannerView.accessibilityLabel? += ". " + String(format: NSLocalizedString("Expires in %@", comment: ""), numberOfDaysText)
// Make sure refresh button is correct size.
cell.layoutIfNeeded()
@@ -384,8 +403,6 @@ private extension MyAppsViewController
cell.tintColor = UIColor.gray
cell.bannerView.iconImageView.isIndicatingActivity = true
- cell.bannerView.betaBadgeView.isHidden = !(installedApp.storeApp?.isBeta ?? false)
-
cell.bannerView.buttonLabel.isHidden = true
cell.bannerView.alpha = 1.0
@@ -393,14 +410,14 @@ private extension MyAppsViewController
cell.deactivateBadge?.alpha = 0.0
cell.deactivateBadge?.transform = CGAffineTransform.identity.scaledBy(x: 0.5, y: 0.5)
+ cell.bannerView.configure(for: installedApp)
+
cell.bannerView.button.isIndicatingActivity = false
cell.bannerView.button.tintColor = tintColor
cell.bannerView.button.setTitle(NSLocalizedString("ACTIVATE", comment: ""), for: .normal)
cell.bannerView.button.removeTarget(self, action: nil, for: .primaryActionTriggered)
cell.bannerView.button.addTarget(self, action: #selector(MyAppsViewController.activateApp(_:)), for: .primaryActionTriggered)
-
- cell.bannerView.titleLabel.text = installedApp.name
- cell.bannerView.subtitleLabel.text = installedApp.storeApp?.developerName ?? NSLocalizedString("Sideloaded", comment: "")
+ cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Activate %@", comment: ""), installedApp.name)
// Make sure refresh button is correct size.
cell.layoutIfNeeded()
diff --git a/AltStore/News/NewsViewController.swift b/AltStore/News/NewsViewController.swift
index ba659c61..82113f1f 100644
--- a/AltStore/News/NewsViewController.swift
+++ b/AltStore/News/NewsViewController.swift
@@ -348,10 +348,9 @@ extension NewsViewController
footerView.layoutMargins.left = self.view.layoutMargins.left
footerView.layoutMargins.right = self.view.layoutMargins.right
- footerView.bannerView.titleLabel.text = storeApp.name
- footerView.bannerView.subtitleLabel.text = storeApp.developerName
+ footerView.bannerView.configure(for: storeApp)
+
footerView.bannerView.tintColor = storeApp.tintColor
- footerView.bannerView.betaBadgeView.isHidden = !storeApp.isBeta
footerView.bannerView.button.addTarget(self, action: #selector(NewsViewController.performAppAction(_:)), for: .primaryActionTriggered)
footerView.tapGestureRecognizer.addTarget(self, action: #selector(NewsViewController.handleTapGesture(_:)))
@@ -359,7 +358,10 @@ extension NewsViewController
if storeApp.installedApp == nil
{
- footerView.bannerView.button.setTitle(NSLocalizedString("FREE", comment: ""), for: .normal)
+ let buttonTitle = NSLocalizedString("Free", comment: "")
+ footerView.bannerView.button.setTitle(buttonTitle.uppercased(), for: .normal)
+ footerView.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Download %@", comment: ""), storeApp.name)
+ footerView.bannerView.button.accessibilityValue = buttonTitle
let progress = AppManager.shared.installationProgress(for: storeApp)
footerView.bannerView.button.progress = progress
@@ -376,6 +378,8 @@ extension NewsViewController
else
{
footerView.bannerView.button.setTitle(NSLocalizedString("OPEN", comment: ""), for: .normal)
+ footerView.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Open %@", comment: ""), storeApp.name)
+ footerView.bannerView.button.accessibilityValue = nil
footerView.bannerView.button.progress = nil
footerView.bannerView.button.countdownDate = nil
}
diff --git a/AltStore/Sources/SourcesViewController.swift b/AltStore/Sources/SourcesViewController.swift
index 7ab840df..fb465271 100644
--- a/AltStore/Sources/SourcesViewController.swift
+++ b/AltStore/Sources/SourcesViewController.swift
@@ -44,7 +44,6 @@ private extension SourcesViewController
cell.tintColor = tintColor
cell.bannerView.iconImageView.isHidden = true
- cell.bannerView.betaBadgeView.isHidden = true
cell.bannerView.buttonLabel.isHidden = true
cell.bannerView.button.isHidden = true
cell.bannerView.button.isIndicatingActivity = false
@@ -53,6 +52,10 @@ private extension SourcesViewController
cell.bannerView.subtitleLabel.text = source.sourceURL.absoluteString
cell.bannerView.subtitleLabel.numberOfLines = 2
+ let attributedLabel = NSAttributedString(string: source.name + "\n" + source.sourceURL.absoluteString, attributes: [.accessibilitySpeechPunctuation: true])
+ cell.bannerView.accessibilityAttributedLabel = attributedLabel
+ cell.bannerView.accessibilityTraits.remove(.button)
+
// Make sure refresh button is correct size.
cell.layoutIfNeeded()
}