Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 74 additions & 20 deletions submodules/AlertUI/Sources/TextAlertWithEntitiesController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ import TextNodeWithEntities

private let alertWidth: CGFloat = 270.0

private final class TextAlertWithEntitiesAccessibilityCustomAction: UIAccessibilityCustomAction {
let perform: () -> Void

init(name: String, target: Any?, selector: Selector, perform: @escaping () -> Void) {
self.perform = perform

super.init(name: name, target: target, selector: selector)
}
}

final class TextAlertWithEntitiesContentNode: AlertContentNode {
private var theme: AlertControllerTheme
private let actionLayout: TextAlertContentActionLayout
Expand All @@ -25,6 +35,10 @@ final class TextAlertWithEntitiesContentNode: AlertContentNode {
return self._dismissOnOutsideTap
}

override public var accessibilityInitialFocusNode: ASDisplayNode? {
return self.titleNode ?? self.textNode
}

private var highlightedItemIndex: Int? = nil

var textAttributeAction: (NSAttributedString.Key, (Any) -> Void)? {
Expand All @@ -47,6 +61,7 @@ final class TextAlertWithEntitiesContentNode: AlertContentNode {
self.textNode.highlightAttributeAction = nil
self.textNode.tapAttributeAction = nil
}
self.updateTextAccessibilityActions()
}
}

Expand All @@ -59,10 +74,11 @@ final class TextAlertWithEntitiesContentNode: AlertContentNode {
titleNode.attributedText = title
titleNode.displaysAsynchronously = false
titleNode.isUserInteractionEnabled = false
titleNode.maximumNumberOfLines = 4
titleNode.maximumNumberOfLines = 0
titleNode.truncationType = .end
titleNode.isAccessibilityElement = true
titleNode.accessibilityLabel = title.string
titleNode.accessibilityTraits = [.header]
self.titleNode = titleNode
} else {
self.titleNode = nil
Expand Down Expand Up @@ -127,6 +143,38 @@ final class TextAlertWithEntitiesContentNode: AlertContentNode {
for separatorNode in self.actionVerticalSeparators {
self.addSubnode(separatorNode)
}

self.updateTextAccessibilityActions()
}

private func updateTextAccessibilityActions() {
guard let attributedText = self.textNode.attributedText, let (attribute, textAttributeAction) = self.textAttributeAction, attributedText.length != 0 else {
self.textNode.accessibilityCustomActions = nil
return
}

var accessibilityActions: [UIAccessibilityCustomAction] = []
attributedText.enumerateAttribute(attribute, in: NSRange(location: 0, length: attributedText.length), options: []) { [weak self] value, range, _ in
guard let self, let value else {
return
}
let actionName = attributedText.attributedSubstring(from: range).string.trimmingCharacters(in: .whitespacesAndNewlines)
guard !actionName.isEmpty else {
return
}
accessibilityActions.append(TextAlertWithEntitiesAccessibilityCustomAction(name: actionName, target: self, selector: #selector(self.performTextAccessibilityAction(_:)), perform: {
textAttributeAction(value)
}))
}
self.textNode.accessibilityCustomActions = accessibilityActions.isEmpty ? nil : accessibilityActions
}

@objc private func performTextAccessibilityAction(_ action: UIAccessibilityCustomAction) -> Bool {
guard let action = action as? TextAlertWithEntitiesAccessibilityCustomAction else {
return false
}
action.perform()
return true
}

func setHighlightedItemIndex(_ index: Int?, update: Bool = false) {
Expand Down Expand Up @@ -199,6 +247,13 @@ final class TextAlertWithEntitiesContentNode: AlertContentNode {
_ = self.updateLayout(size: size, transition: .immediate)
}
}

override func contentSizeCategoryUpdated() {
for actionNode in self.actionNodes {
actionNode.updateTheme(self.theme)
}
self.requestLayout?(.immediate)
}

override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
self.validLayout = size
Expand All @@ -214,34 +269,31 @@ final class TextAlertWithEntitiesContentNode: AlertContentNode {
}
let textSize = self.textNode.updateLayout(CGSize(width: size.width - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude))

let actionButtonHeight: CGFloat = 44.0

var minActionsWidth: CGFloat = 0.0
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
let actionTitleInsets: CGFloat = 8.0
let minimumActionButtonHeight: CGFloat = 44.0
let maxActionWidth: CGFloat = self.actionNodes.isEmpty ? size.width : floor(size.width / CGFloat(self.actionNodes.count))

var effectiveActionLayout = self.actionLayout
if self.traitCollection.preferredContentSizeCategory.isAccessibilityCategory {
effectiveActionLayout = .vertical
}
var actionHeights: [CGFloat] = []
for actionNode in self.actionNodes {
let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight))
if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 {
let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: max(1.0, maxActionWidth - 16.0), height: CGFloat.greatestFiniteMagnitude))
let actionHeight = max(minimumActionButtonHeight, actionTitleSize.height + 20.0)
actionHeights.append(actionHeight)
if case .horizontal = effectiveActionLayout, actionHeight > minimumActionButtonHeight {
effectiveActionLayout = .vertical
}
switch effectiveActionLayout {
case .horizontal:
minActionsWidth += actionTitleSize.width + actionTitleInsets
case .vertical:
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
}
}

let resultSize: CGSize

var actionsHeight: CGFloat = 0.0
switch effectiveActionLayout {
case .horizontal:
actionsHeight = actionButtonHeight
actionsHeight = actionHeights.max() ?? minimumActionButtonHeight
case .vertical:
actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
actionsHeight = actionHeights.reduce(0.0, +)
}

let contentWidth = alertWidth - insets.left - insets.right
Expand All @@ -261,10 +313,11 @@ final class TextAlertWithEntitiesContentNode: AlertContentNode {
resultSize = CGSize(width: contentWidth + insets.left + insets.right, height: textSize.height + actionsHeight + insets.top + insets.bottom)
}

self.actionNodesSeparator.isHidden = self.actionNodes.isEmpty
self.actionNodesSeparator.frame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))

var actionOffset: CGFloat = 0.0
let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count))
let actionWidth: CGFloat = self.actionNodes.isEmpty ? resultSize.width : floor(resultSize.width / CGFloat(self.actionNodes.count))
var separatorIndex = -1
var nodeIndex = 0
for actionNode in self.actionNodes {
Expand Down Expand Up @@ -294,11 +347,12 @@ final class TextAlertWithEntitiesContentNode: AlertContentNode {
let actionNodeFrame: CGRect
switch effectiveActionLayout {
case .horizontal:
actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionsHeight))
actionOffset += currentActionWidth
case .vertical:
actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
actionOffset += actionButtonHeight
let actionHeight = actionHeights[nodeIndex]
actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionHeight))
actionOffset += actionHeight
}

transition.updateFrame(node: actionNode, frame: actionNodeFrame)
Expand Down
26 changes: 26 additions & 0 deletions submodules/Display/Source/Accessibility.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,32 @@ public func addAccessibilityChildren(of node: ASDisplayNode, container: Any, to
}
}

public func firstAccessibilityElement(in view: UIView) -> Any? {
guard !view.isHidden, view.alpha > 0.01, !view.accessibilityElementsHidden else {
return nil
}
if view.isAccessibilityElement {
return view
}
if let accessibilityElements = view.accessibilityElements {
for element in accessibilityElements {
if let elementView = element as? UIView {
if let result = firstAccessibilityElement(in: elementView) {
return result
}
} else {
return element
}
}
}
for subview in view.subviews {
if let result = firstAccessibilityElement(in: subview) {
return result
}
}
return nil
}

public func smartInvertColorsEnabled() -> Bool {
if #available(iOSApplicationExtension 11.0, iOS 11.0, *), UIAccessibility.isInvertColorsEnabled {
return true
Expand Down
35 changes: 32 additions & 3 deletions submodules/Display/Source/AccessibilityAreaNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,45 @@ public protocol AccessibilityFocusableNode {
}

public final class AccessibilityAreaNode: ASDisplayNode {
public var activate: (() -> Bool)?
public var increment: (() -> Void)?
public var decrement: (() -> Void)?
public var activate: (() -> Bool)? {
didSet {
self.updateRespondsToUserInteraction()
}
}
public var increment: (() -> Void)? {
didSet {
self.updateRespondsToUserInteraction()
}
}
public var decrement: (() -> Void)? {
didSet {
self.updateRespondsToUserInteraction()
}
}
public var focused: (() -> Void)?

override public init() {
super.init()

self.isAccessibilityElement = true
}

override public func didLoad() {
super.didLoad()

self.updateRespondsToUserInteraction()
}

private func updateRespondsToUserInteraction() {
if self.isNodeLoaded {
self.view.accessibilityRespondsToUserInteraction = self.activate != nil
|| self.increment != nil
|| self.decrement != nil
|| self.accessibilityTraits.contains(.button)
|| self.accessibilityTraits.contains(.link)
|| self.accessibilityTraits.contains(.adjustable)
}
}

override public func accessibilityActivate() -> Bool {
return self.activate?() ?? false
Expand Down
18 changes: 6 additions & 12 deletions submodules/Display/Source/ActionSheetButtonItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,6 @@ public class ActionSheetButtonItem: ActionSheetItem {
public class ActionSheetButtonNode: ActionSheetItemNode {
private let theme: ActionSheetControllerTheme

private let defaultFont: UIFont
private let boldFont: UIFont

private var item: ActionSheetButtonItem?

private let button: HighlightTrackingButton
Expand All @@ -61,16 +58,13 @@ public class ActionSheetButtonNode: ActionSheetItemNode {

override public init(theme: ActionSheetControllerTheme) {
self.theme = theme

self.defaultFont = Font.regular(floor(theme.baseFontSize * 20.0 / 17.0))
self.boldFont = Font.medium(floor(theme.baseFontSize * 20.0 / 17.0))


self.button = HighlightTrackingButton()
self.button.isAccessibilityElement = false

self.label = ImmediateTextNode()
self.label.isUserInteractionEnabled = false
self.label.maximumNumberOfLines = 1
self.label.maximumNumberOfLines = 0
self.label.displaysAsynchronously = false
self.label.truncationType = .end

Expand Down Expand Up @@ -146,9 +140,9 @@ public class ActionSheetButtonNode: ActionSheetItemNode {
}
switch item.font {
case .default:
textFont = Font.regular(floor(theme.baseFontSize * 20.0 / 17.0))
textFont = Font.regular(UIFontMetrics(forTextStyle: .body).scaledValue(for: floor(theme.baseFontSize * 20.0 / 17.0)))
case .bold:
textFont = Font.medium(floor(theme.baseFontSize * 20.0 / 17.0))
textFont = Font.medium(UIFontMetrics(forTextStyle: .body).scaledValue(for: floor(theme.baseFontSize * 20.0 / 17.0)))
}
self.label.attributedText = NSAttributedString(string: item.title, font: textFont, textColor: textColor)
self.label.isAccessibilityElement = false
Expand All @@ -165,11 +159,11 @@ public class ActionSheetButtonNode: ActionSheetItemNode {
}

public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
let size = CGSize(width: constrainedSize.width, height: 57.0)
let labelSize = self.label.updateLayout(CGSize(width: max(1.0, constrainedSize.width - 32.0), height: constrainedSize.height))
let size = CGSize(width: constrainedSize.width, height: max(57.0, labelSize.height + 28.0))

self.button.frame = CGRect(origin: CGPoint(), size: size)

let labelSize = self.label.updateLayout(CGSize(width: max(1.0, size.width - 10.0), height: size.height))
self.label.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - labelSize.width) / 2.0), y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize)
self.accessibilityArea.frame = CGRect(origin: CGPoint(), size: size)

Expand Down
Loading