Accessibility updates

This commit is contained in:
Peter
2019-03-19 01:24:06 +04:00
parent 9bf4297c60
commit aba18c4a92
17 changed files with 338 additions and 75 deletions

View File

@@ -505,6 +505,18 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
return
}
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withToggledSelectedMessages(ids, value: value) } })
if let selectionState = strongSelf.presentationInterfaceState.interfaceState.selectionState {
let count = selectionState.selectedIds.count
let text: String
if count == 1 {
text = "1 message selected"
} else {
text = "\(count) messages selected"
}
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: {
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, text as NSString)
})
}
}, sendMessage: { [weak self] text in
guard let strongSelf = self, canSendMessagesToChat(strongSelf.presentationInterfaceState) else {
return
@@ -2190,6 +2202,17 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
}, beginMessageSelection: { [weak self] messageIds in
if let strongSelf = self, strongSelf.isNodeLoaded {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true,{ $0.updatedInterfaceState { $0.withUpdatedSelectedMessages(messageIds) } })
if let selectionState = strongSelf.presentationInterfaceState.interfaceState.selectionState {
let count = selectionState.selectedIds.count
let text: String
if count == 1 {
text = "1 message selected"
} else {
text = "\(count) messages selected"
}
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, text)
}
}
}, deleteSelectedMessages: { [weak self] in
if let strongSelf = self {

View File

@@ -1795,6 +1795,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
})
self.messageActionSheetController = nil
self.messageActionSheetControllerAdditionalInset = nil
self.accessibilityElementsHidden = false
}
if let stableId = self.controllerInteraction.contextHighlightedState?.messageStableId {
let contextMenuController = displayContextMenuController?.0
@@ -1803,6 +1804,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
contextMenuController?.dismiss()
}, associatedController: contextMenuController)
self.messageActionSheetController = (controller, stableId)
self.accessibilityElementsHidden = true
if let sheetActions = sheetActions, !sheetActions.isEmpty {
self.controllerInteraction.presentGlobalOverlayController(controller, nil)
}

View File

@@ -599,6 +599,7 @@ private func canPerformDeleteActions(limits: LimitsConfiguration, accountPeerId:
return true
}
if message.flags.contains(.Incoming) {
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
if message.id.peerId.namespace == Namespaces.Peer.CloudUser {
if message.timestamp + limits.maxMessageRevokeIntervalInPrivateChats > timestamp {
@@ -609,6 +610,7 @@ private func canPerformDeleteActions(limits: LimitsConfiguration, accountPeerId:
return true
}
}
}
return false
}

View File

@@ -142,6 +142,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
private var shareButtonNode: HighlightableButtonNode?
private let messageAccessibilityArea: AccessibilityAreaNode
private var backgroundType: ChatMessageBackgroundType?
private var highlightedState: Bool = false
@@ -164,10 +166,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
required init() {
self.backgroundNode = ChatMessageBackground()
self.messageAccessibilityArea = AccessibilityAreaNode()
super.init(layerBacked: false)
self.addSubnode(self.backgroundNode)
self.addSubnode(self.messageAccessibilityArea)
}
required init?(coder aDecoder: NSCoder) {
@@ -317,8 +321,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
let currentItem = self.appliedItem
let currentForwardInfo = self.appliedForwardInfo
let isSelected = self.selectionNode?.selected
return { item, params, mergedTop, mergedBottom, dateHeaderAtBottom in
let accessibilityData = ChatMessageAccessibilityData(item: item)
let accessibilityData = ChatMessageAccessibilityData(item: item, isSelected: isSelected)
let baseWidth = params.width - params.leftInset - params.rightInset
@@ -1157,8 +1163,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
if let strongSelf = self {
strongSelf.appliedItem = item
strongSelf.appliedForwardInfo = (forwardSource, forwardAuthorSignature)
strongSelf.accessibilityLabel = accessibilityData.label
strongSelf.accessibilityValue = accessibilityData.value
strongSelf.updateAccessibilityData(accessibilityData)
var transition: ContainedViewLayoutTransition = .immediate
if case let .System(duration) = animation {
@@ -1197,7 +1202,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
}
})
strongSelf.deliveryFailedNode = deliveryFailedNode
strongSelf.addSubnode(deliveryFailedNode)
strongSelf.insertSubnode(deliveryFailedNode, belowSubnode: strongSelf.messageAccessibilityArea)
}
let deliveryFailedSize = deliveryFailedNode.updateLayout(theme: item.presentationData.theme.theme)
let deliveryFailedFrame = CGRect(origin: CGPoint(x: backgroundFrame.maxX + deliveryFailedInset - deliveryFailedSize.width, y: backgroundFrame.maxY - deliveryFailedSize.height), size: deliveryFailedSize)
@@ -1221,7 +1226,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
if !nameNode.isNodeLoaded {
nameNode.isUserInteractionEnabled = false
}
strongSelf.addSubnode(nameNode)
strongSelf.insertSubnode(nameNode, belowSubnode: strongSelf.messageAccessibilityArea)
}
nameNode.frame = CGRect(origin: CGPoint(x: contentOrigin.x + layoutConstants.text.bubbleInsets.left, y: layoutConstants.bubble.contentInsets.top + nameNodeOriginY), size: nameNodeSizeApply.0)
@@ -1232,7 +1237,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
if !adminBadgeNode.isNodeLoaded {
adminBadgeNode.isUserInteractionEnabled = false
}
strongSelf.addSubnode(adminBadgeNode)
strongSelf.insertSubnode(adminBadgeNode, belowSubnode: strongSelf.messageAccessibilityArea)
adminBadgeNode.frame = adminBadgeFrame
} else {
let previousAdminBadgeFrame = adminBadgeNode.frame
@@ -1254,7 +1259,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
strongSelf.forwardInfoNode = forwardInfoNode
var animateFrame = true
if forwardInfoNode.supernode == nil {
strongSelf.addSubnode(forwardInfoNode)
strongSelf.insertSubnode(forwardInfoNode, belowSubnode: strongSelf.messageAccessibilityArea)
animateFrame = false
}
let previousForwardInfoNodeFrame = forwardInfoNode.frame
@@ -1273,7 +1278,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
strongSelf.replyInfoNode = replyInfoNode
var animateFrame = true
if replyInfoNode.supernode == nil {
strongSelf.addSubnode(replyInfoNode)
strongSelf.insertSubnode(replyInfoNode, belowSubnode: strongSelf.messageAccessibilityArea)
animateFrame = false
}
let previousReplyInfoNodeFrame = replyInfoNode.frame
@@ -1306,9 +1311,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
}
if let addedContentNodes = addedContentNodes {
for (contentNodeMessage, contentNode) in addedContentNodes {
for (_, contentNode) in addedContentNodes {
updatedContentNodes.append(contentNode)
strongSelf.addSubnode(contentNode)
strongSelf.insertSubnode(contentNode, belowSubnode: strongSelf.messageAccessibilityArea)
contentNode.visibility = strongSelf.visibility
}
@@ -1376,7 +1381,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
if mosaicStatusNode !== strongSelf.mosaicStatusNode {
strongSelf.mosaicStatusNode?.removeFromSupernode()
strongSelf.mosaicStatusNode = mosaicStatusNode
strongSelf.addSubnode(mosaicStatusNode)
strongSelf.insertSubnode(mosaicStatusNode, belowSubnode: strongSelf.messageAccessibilityArea)
}
let absoluteOrigin = mosaicStatusOrigin.offsetBy(dx: contentOrigin.x, dy: contentOrigin.y)
mosaicStatusNode.frame = CGRect(origin: CGPoint(x: absoluteOrigin.x - layoutConstants.image.statusInsets.right - size.width, y: absoluteOrigin.y - layoutConstants.image.statusInsets.bottom - size.height), size: size)
@@ -1391,7 +1396,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
shareButtonNode.removeFromSupernode()
}
strongSelf.shareButtonNode = updatedShareButtonNode
strongSelf.addSubnode(updatedShareButtonNode)
strongSelf.insertSubnode(updatedShareButtonNode, belowSubnode: strongSelf.messageAccessibilityArea)
updatedShareButtonNode.addTarget(strongSelf, action: #selector(strongSelf.shareButtonPressed), forControlEvents: .touchUpInside)
}
if let updatedShareButtonBackground = updatedShareButtonBackground {
@@ -1417,6 +1422,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
strongSelf.backgroundFrameTransition = nil
}
strongSelf.backgroundNode.frame = backgroundFrame
strongSelf.messageAccessibilityArea.frame = backgroundFrame
if let shareButtonNode = strongSelf.shareButtonNode {
shareButtonNode.frame = CGRect(origin: CGPoint(x: backgroundFrame.maxX + 8.0, y: backgroundFrame.maxY - 30.0), size: CGSize(width: 29.0, height: 29.0))
}
@@ -1448,7 +1454,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
strongSelf.presentMessageButtonContextMenu(button: button)
}
}
strongSelf.addSubnode(actionButtonsNode)
strongSelf.insertSubnode(actionButtonsNode, belowSubnode: strongSelf.messageAccessibilityArea)
} else {
if case let .System(duration) = animation {
actionButtonsNode.layer.animateFrame(from: previousFrame, to: actionButtonsFrame, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
@@ -1465,11 +1471,18 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
}
}
private func updateAccessibilityData(_ accessibilityData: ChatMessageAccessibilityData) {
self.messageAccessibilityArea.accessibilityLabel = accessibilityData.label
self.messageAccessibilityArea.accessibilityValue = accessibilityData.value
self.messageAccessibilityArea.accessibilityHint = accessibilityData.hint
self.messageAccessibilityArea.accessibilityTraits = accessibilityData.traits
}
private func addContentNode(node: ChatMessageBubbleContentNode) {
if let transitionClippingNode = self.transitionClippingNode {
transitionClippingNode.addSubnode(node)
} else {
self.addSubnode(node)
self.insertSubnode(node, belowSubnode: self.messageAccessibilityArea)
}
}
@@ -1490,7 +1503,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
for contentNode in self.contentNodes {
node.addSubnode(contentNode)
}
self.addSubnode(node)
self.insertSubnode(node, belowSubnode: self.messageAccessibilityArea)
self.transitionClippingNode = node
}
}
@@ -1498,13 +1511,13 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
private func disableTransitionClippingNode() {
if let transitionClippingNode = self.transitionClippingNode {
if let forwardInfoNode = self.forwardInfoNode {
self.addSubnode(forwardInfoNode)
self.insertSubnode(forwardInfoNode, belowSubnode: self.messageAccessibilityArea)
}
if let replyInfoNode = self.replyInfoNode {
self.addSubnode(replyInfoNode)
self.insertSubnode(replyInfoNode, belowSubnode: self.messageAccessibilityArea)
}
for contentNode in self.contentNodes {
self.addSubnode(contentNode)
self.insertSubnode(contentNode, belowSubnode: self.messageAccessibilityArea)
}
transitionClippingNode.removeFromSupernode()
self.transitionClippingNode = nil
@@ -1525,6 +1538,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
if let backgroundFrameTransition = self.backgroundFrameTransition {
let backgroundFrame = CGRect.interpolator()(backgroundFrameTransition.0, backgroundFrameTransition.1, progress) as! CGRect
self.backgroundNode.frame = backgroundFrame
self.messageAccessibilityArea.frame = backgroundFrame
if let shareButtonNode = self.shareButtonNode {
shareButtonNode.frame = CGRect(origin: CGPoint(x: backgroundFrame.maxX + 8.0, y: backgroundFrame.maxY - 30.0), size: CGSize(width: 29.0, height: 29.0))
@@ -1881,6 +1895,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
return
}
let wasSelected = self.selectionNode?.selected
var canHaveSelection = true
switch item.content {
case let .message(message, _, _, _):
@@ -1935,7 +1951,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
})
selectionNode.frame = CGRect(origin: CGPoint(x: -offset, y: 0.0), size: CGSize(width: self.contentBounds.size.width, height: self.contentBounds.size.height))
self.addSubnode(selectionNode)
self.insertSubnode(selectionNode, belowSubnode: self.messageAccessibilityArea)
self.selectionNode = selectionNode
selectionNode.updateSelected(selected, animated: false)
let previousSubnodeTransform = self.subnodeTransform
@@ -1969,6 +1985,11 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
}
}
}
let isSelected = self.selectionNode?.selected
if wasSelected != isSelected {
self.updateAccessibilityData(ChatMessageAccessibilityData(item: item, isSelected: isSelected))
}
}
override func updateSearchTextHighlightState() {
@@ -2055,7 +2076,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
let swipeToReplyNode = ChatMessageSwipeToReplyNode(fillColor: bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.bubble.shareButtonFillColor, wallpaper: item.presentationData.theme.wallpaper), strokeColor: bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.bubble.shareButtonStrokeColor, wallpaper: item.presentationData.theme.wallpaper), foregroundColor: bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.bubble.shareButtonForegroundColor, wallpaper: item.presentationData.theme.wallpaper))
self.swipeToReplyNode = swipeToReplyNode
self.addSubnode(swipeToReplyNode)
self.insertSubnode(swipeToReplyNode, belowSubnode: self.messageAccessibilityArea)
animateReplyNodeIn = true
}
}

View File

@@ -98,35 +98,131 @@ enum ChatMessagePeekPreviewContent {
case url(ASDisplayNode, CGRect, String)
}
private let voiceMessageDurationFormatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .spellOut
formatter.allowedUnits = [.second]
formatter.zeroFormattingBehavior = .pad
return formatter
}()
private let musicDurationFormatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .spellOut
formatter.allowedUnits = [.minute, .second]
formatter.zeroFormattingBehavior = .pad
return formatter
}()
final class ChatMessageAccessibilityData {
let label: String?
let value: String?
let hint: String?
let traits: UIAccessibilityTraits
init(item: ChatMessageItem) {
let label: String
init(item: ChatMessageItem, isSelected: Bool?) {
var label: String
let value: String
var hint: String?
var traits: UIAccessibilityTraits = 0
let isIncoming = item.message.effectivelyIncoming(item.context.account.peerId)
var announceIncomingAuthors = false
if let peer = item.message.peers[item.message.id.peerId] {
if peer is TelegramGroup {
announceIncomingAuthors = true
} else if let channel = peer as? TelegramChannel, case .group = channel.info {
announceIncomingAuthors = true
}
}
var authorName: String?
if let author = item.message.author {
if item.message.effectivelyIncoming(item.context.account.peerId) {
authorName = author.displayTitle
if isIncoming {
label = author.displayTitle
} else {
label = "Outgoing message"
label = "Your message"
}
} else {
label = "Post"
label = "Message"
}
if let chatPeer = item.message.peers[item.message.id.peerId] {
let (_, _, messageText) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, message: item.message, chatPeer: RenderedPeer(peer: chatPeer), accountPeerId: item.context.account.peerId)
var text = messageText
loop: for media in item.message.media {
if let file = media as? TelegramMediaFile {
for attribute in file.attributes {
switch attribute {
case let .Audio(audio):
if isSelected == nil {
hint = "Double tap to play"
}
traits |= UIAccessibilityTraitStartsMediaSession
if audio.isVoice {
let durationString = voiceMessageDurationFormatter.string(from: Double(audio.duration)) ?? ""
if isIncoming {
if announceIncomingAuthors, let authorName = authorName {
label = "Voice message, from: \(authorName)"
} else {
label = "Voice message"
}
} else {
label = "Your voice message"
}
text = "Duration: \(durationString)"
} else {
let durationString = musicDurationFormatter.string(from: Double(audio.duration)) ?? ""
if announceIncomingAuthors, let authorName = authorName {
label = "Music file, from: \(authorName)"
} else {
label = "Your music file"
}
let performer = audio.performer ?? "Unknown"
let title = audio.title ?? "Unknown"
text = "\(title), by \(performer). Duration: \(durationString)"
}
default:
break
}
}
break loop
}
}
var result = ""
result += "\(messageText)"
if let isSelected = isSelected {
if isSelected {
result += "Selected.\n"
}
traits |= UIAccessibilityTraitStartsMediaSession
}
result += "\(text)"
let dateString = DateFormatter.localizedString(from: Date(timeIntervalSince1970: Double(item.message.timestamp)), dateStyle: DateFormatter.Style.medium, timeStyle: DateFormatter.Style.short)
result += "\n\(dateString)"
if !isIncoming && item.read {
if announceIncomingAuthors {
result += "Seen by recipients"
} else {
result += "Seen by recipient"
}
}
value = result
} else {
value = "Empty"
value = ""
}
self.label = label
self.value = value
self.hint = hint
self.traits = traits
}
}
@@ -142,8 +238,6 @@ public class ChatMessageItemView: ListViewItemNode {
public init(layerBacked: Bool) {
super.init(layerBacked: layerBacked, dynamicBounce: true, rotated: true)
self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
self.isAccessibilityElement = true
}
required public init?(coder aDecoder: NSCoder) {

View File

@@ -4,7 +4,7 @@ import AsyncDisplayKit
final class ChatMessageSelectionNode: ASDisplayNode {
private let toggle: (Bool) -> Void
private var selected = false
private(set) var selected = false
private let checkNode: CheckNode
init(theme: PresentationTheme, toggle: @escaping (Bool) -> Void) {

View File

@@ -50,12 +50,15 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
self.playButton = HighlightableButtonNode()
self.playButton.displaysAsynchronously = false
self.playButton.setImage(generatePlayIcon(theme), for: [])
self.playButton.isUserInteractionEnabled = false
self.pauseButton = HighlightableButtonNode()
self.pauseButton.displaysAsynchronously = false
self.pauseButton.setImage(generatePauseIcon(theme), for: [])
self.pauseButton.isHidden = true
self.pauseButton.isUserInteractionEnabled = false
self.waveformButton = ASButtonNode()
self.waveformButton.accessibilityTraits |= UIAccessibilityTraitStartsMediaSession
self.waveformNode = AudioWaveformNode()
self.waveformNode.isLayerBacked = true
@@ -100,6 +103,11 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
if self.presentationInterfaceState?.recordedMediaPreview != interfaceState.recordedMediaPreview {
updateWaveform = true
}
if self.presentationInterfaceState?.strings !== interfaceState.strings {
self.deleteButton.accessibilityLabel = "Delete"
self.sendButton.accessibilityLabel = "Send"
self.waveformButton.accessibilityLabel = "Preview voice message"
}
self.presentationInterfaceState = interfaceState
if let recordedMediaPreview = interfaceState.recordedMediaPreview, updateWaveform {

View File

@@ -42,12 +42,20 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode {
}
func updateAccessibility() {
if self.sendButton.alpha.isZero {
self.accessibilityTraits = UIAccessibilityTraitButton | UIAccessibilityTraitNotEnabled
self.accessibilityLabel = "Send"
if !self.micButton.alpha.isZero {
self.accessibilityTraits = UIAccessibilityTraitButton
switch self.micButton.mode {
case .audio:
self.accessibilityLabel = "Voice Message"
self.accessibilityHint = "Double tap and hold to record voice message. Slide up to pin recording, slide left to cancel. Double tap to switch to video."
case .video:
self.accessibilityLabel = "Video Message"
self.accessibilityHint = "Double tap and hold to record voice message. Slide up to pin recording, slide left to cancel. Double tap to switch to audio."
}
} else {
self.accessibilityTraits = UIAccessibilityTraitButton
self.accessibilityLabel = "Send"
self.accessibilityHint = nil
}
}
}

View File

@@ -196,15 +196,16 @@ enum ChatTextInputPanelPasteData {
}
class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
var textPlaceholderNode: TextNode
var textPlaceholderNode: ImmediateTextNode
var contextPlaceholderNode: TextNode?
let textInputContainer: ASDisplayNode
var textInputNode: EditableTextNode?
let textInputBackgroundView: UIImageView
let actionButtons: ChatTextInputActionButtonsNode
var mediaRecordingAccessibilityArea: AccessibilityAreaNode?
let attachmentButton: HighlightableButton
let attachmentButton: HighlightableButtonNode
let searchLayoutClearButton: HighlightableButton
let searchLayoutProgressView: UIImageView
var audioRecordingInfoContainerNode: ASDisplayNode?
@@ -321,9 +322,12 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
self.textInputContainer.backgroundColor = theme.chat.inputPanel.inputBackgroundColor
self.textInputBackgroundView = UIImageView()
self.textPlaceholderNode = TextNode()
self.textPlaceholderNode = ImmediateTextNode()
self.textPlaceholderNode.maximumNumberOfLines = 1
self.textPlaceholderNode.isLayerBacked = true
self.attachmentButton = HighlightableButton()
self.attachmentButton = HighlightableButtonNode()
self.attachmentButton.accessibilityLabel = "Send media"
self.attachmentButton.isAccessibilityElement = true
self.searchLayoutClearButton = HighlightableButton()
self.searchLayoutProgressView = UIImageView(image: searchLayoutProgressImage)
self.searchLayoutProgressView.isHidden = true
@@ -332,8 +336,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
super.init()
self.attachmentButton.addTarget(self, action: #selector(self.attachmentButtonPressed), for: .touchUpInside)
self.view.addSubview(self.attachmentButton)
self.attachmentButton.addTarget(self, action: #selector(self.attachmentButtonPressed), forControlEvents: .touchUpInside)
self.addSubnode(self.attachmentButton)
self.addSubnode(self.actionButtons)
@@ -495,6 +499,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
}
}
textInputNode.view.addGestureRecognizer(recognizer)
textInputNode.textView.accessibilityHint = self.textPlaceholderNode.attributedText?.string
}
private func textFieldMaxHeight(_ maxHeight: CGFloat, metrics: LayoutMetrics) -> CGFloat {
@@ -688,7 +694,9 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
self.currentPlaceholder = placeholder
let placeholderLayout = TextNode.asyncLayout(self.textPlaceholderNode)
let baseFontSize = max(17.0, interfaceState.fontSize.baseDisplaySize)
let (placeholderSize, placeholderApply) = placeholderLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: placeholder, font: Font.regular(baseFontSize), textColor: interfaceState.theme.chat.inputPanel.inputPlaceholderColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 320.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
self.textPlaceholderNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(baseFontSize), textColor: interfaceState.theme.chat.inputPanel.inputPlaceholderColor)
self.textInputNode?.textView.accessibilityHint = placeholder
let placeholderSize = self.textPlaceholderNode.updateLayout(CGSize(width: 320.0, height: CGFloat.greatestFiniteMagnitude))
if transition.isAnimated, let snapshotLayer = self.textPlaceholderNode.layer.snapshotContentTree() {
self.textPlaceholderNode.supernode?.layer.insertSublayer(snapshotLayer, above: self.textPlaceholderNode.layer)
snapshotLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.22, removeOnCompletion: false, completion: { [weak snapshotLayer] _ in
@@ -696,8 +704,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
})
self.textPlaceholderNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18)
}
self.textPlaceholderNode.frame = CGRect(origin: self.textPlaceholderNode.frame.origin, size: placeholderSize.size)
let _ = placeholderApply()
self.textPlaceholderNode.frame = CGRect(origin: self.textPlaceholderNode.frame.origin, size: placeholderSize)
}
}
@@ -953,11 +960,48 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
textInputBackgroundWidthOffset = 36.0
}
transition.updateFrame(node: self.actionButtons, frame: CGRect(origin: CGPoint(x: width - rightInset - 43.0 - UIScreenPixel + composeButtonsOffset, y: panelHeight - minimalHeight), size: CGSize(width: 44.0, height: minimalHeight)))
let actionButtonsFrame = CGRect(origin: CGPoint(x: width - rightInset - 43.0 - UIScreenPixel + composeButtonsOffset, y: panelHeight - minimalHeight), size: CGSize(width: 44.0, height: minimalHeight))
transition.updateFrame(node: self.actionButtons, frame: actionButtonsFrame)
if let presentationInterfaceState = self.presentationInterfaceState {
self.actionButtons.updateLayout(size: CGSize(width: 44.0, height: minimalHeight), transition: transition, interfaceState: presentationInterfaceState)
}
if let mediaRecordingState = interfaceState.inputTextPanelState.mediaRecordingState {
let text: String = "Send"
let mediaRecordingAccessibilityArea: AccessibilityAreaNode
var added = false
if let current = self.mediaRecordingAccessibilityArea {
mediaRecordingAccessibilityArea = current
} else {
added = true
mediaRecordingAccessibilityArea = AccessibilityAreaNode()
mediaRecordingAccessibilityArea.accessibilityLabel = text
mediaRecordingAccessibilityArea.accessibilityTraits = UIAccessibilityTraitButton | UIAccessibilityTraitStartsMediaSession
self.mediaRecordingAccessibilityArea = mediaRecordingAccessibilityArea
mediaRecordingAccessibilityArea.activate = { [weak self] in
self?.interfaceInteraction?.finishMediaRecording(.send)
return true
}
self.insertSubnode(mediaRecordingAccessibilityArea, aboveSubnode: self.actionButtons)
}
self.actionButtons.isAccessibilityElement = false
let size: CGFloat = 120.0
mediaRecordingAccessibilityArea.frame = CGRect(origin: CGPoint(x: actionButtonsFrame.midX - size / 2.0, y: actionButtonsFrame.midY - size / 2.0), size: CGSize(width: size, height: size))
if added {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.4, execute: {
[weak mediaRecordingAccessibilityArea] in
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, mediaRecordingAccessibilityArea?.view)
})
}
} else {
self.actionButtons.isAccessibilityElement = true
if let mediaRecordingAccessibilityArea = self.mediaRecordingAccessibilityArea {
self.mediaRecordingAccessibilityArea = nil
mediaRecordingAccessibilityArea.removeFromSupernode()
}
}
let searchLayoutClearButtonSize = CGSize(width: 44.0, height: minimalHeight)
let textFieldInsets = self.textFieldInsets(metrics: metrics)
transition.updateFrame(layer: self.searchLayoutClearButton.layer, frame: CGRect(origin: CGPoint(x: width - rightInset - textFieldInsets.left - textFieldInsets.right + textInputBackgroundWidthOffset + 3.0, y: panelHeight - minimalHeight), size: searchLayoutClearButtonSize))
@@ -1223,6 +1267,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
}
}
}
self.actionButtons.updateAccessibility()
}
private func updateTextHeight() {

View File

@@ -89,6 +89,8 @@ class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
private let titleNode: TextNode
private let activateArea: AccessibilityAreaNode
private var item: ItemListActionItem?
var tag: ItemListItemTag? {
@@ -114,11 +116,13 @@ class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isLayerBacked = true
self.activateArea = AccessibilityAreaNode()
super.init(layerBacked: false, dynamicBounce: false)
self.isAccessibilityElement = true
self.addSubnode(self.titleNode)
self.addSubnode(self.activateArea)
}
func asyncLayout() -> (_ item: ItemListActionItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
@@ -172,7 +176,9 @@ class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
if let strongSelf = self {
strongSelf.item = item
strongSelf.accessibilityLabel = item.title
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height))
strongSelf.activateArea.accessibilityLabel = item.title
var accessibilityTraits: UIAccessibilityTraits = UIAccessibilityTraitButton
switch item.kind {
case .disabled:
@@ -180,7 +186,7 @@ class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
default:
break
}
strongSelf.accessibilityTraits = accessibilityTraits
strongSelf.activateArea.accessibilityTraits = accessibilityTraits
if let _ = updatedTheme {
strongSelf.topStripeNode.backgroundColor = itemSeparatorColor

View File

@@ -110,6 +110,8 @@ class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
let labelBadgeNode: ASImageNode
let labelImageNode: ASImageNode
private let activateArea: AccessibilityAreaNode
private var item: ItemListDisclosureItem?
override var canBeSelected: Bool {
@@ -159,13 +161,15 @@ class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isLayerBacked = true
super.init(layerBacked: false, dynamicBounce: false)
self.activateArea = AccessibilityAreaNode()
self.isAccessibilityElement = true
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.titleNode)
self.addSubnode(self.labelNode)
self.addSubnode(self.arrowNode)
self.addSubnode(self.activateArea)
}
func asyncLayout() -> (_ item: ItemListDisclosureItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
@@ -294,9 +298,14 @@ class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
if let strongSelf = self {
strongSelf.item = item
strongSelf.accessibilityLabel = item.title
strongSelf.accessibilityValue = item.label
strongSelf.accessibilityTraits = UIAccessibilityTraitButton
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height))
strongSelf.activateArea.accessibilityLabel = item.title
strongSelf.activateArea.accessibilityValue = item.label
if item.enabled {
strongSelf.activateArea.accessibilityTraits = 0
} else {
strongSelf.activateArea.accessibilityTraits = UIAccessibilityTraitNotEnabled
}
if let icon = item.icon {
if strongSelf.iconNode.supernode == nil {

View File

@@ -95,6 +95,8 @@ class ItemListMultilineTextItemNode: ListViewItemNode {
private let textNode: TextNode
private let activateArea: AccessibilityAreaNode
private var item: ItemListMultilineTextItem?
var tag: Any? {
@@ -124,9 +126,12 @@ class ItemListMultilineTextItemNode: ListViewItemNode {
self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isLayerBacked = true
self.activateArea = AccessibilityAreaNode()
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.textNode)
self.addSubnode(self.activateArea)
}
override func didLoad() {
@@ -201,6 +206,9 @@ class ItemListMultilineTextItemNode: ListViewItemNode {
if let strongSelf = self {
strongSelf.item = item
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height))
strongSelf.activateArea.accessibilityLabel = item.text
if let _ = updatedTheme {
strongSelf.topStripeNode.backgroundColor = itemSeparatorColor
strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor

View File

@@ -87,6 +87,8 @@ class ItemListPeerActionItemNode: ListViewItemNode {
private let iconNode: ASImageNode
private let titleNode: TextNode
private let activateArea: AccessibilityAreaNode
private var item: ItemListPeerActionItem?
init() {
@@ -112,12 +114,14 @@ class ItemListPeerActionItemNode: ListViewItemNode {
self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isLayerBacked = true
super.init(layerBacked: false, dynamicBounce: false)
self.activateArea = AccessibilityAreaNode()
self.isAccessibilityElement = true
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.iconNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.activateArea)
}
func asyncLayout() -> (_ item: ItemListPeerActionItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) {
@@ -149,7 +153,8 @@ class ItemListPeerActionItemNode: ListViewItemNode {
if let strongSelf = self {
strongSelf.item = item
strongSelf.accessibilityLabel = item.title
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height))
strongSelf.activateArea.accessibilityLabel = item.title
if let _ = updatedTheme {
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor

View File

@@ -505,7 +505,7 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNode {
combinedValueString.append(statusString)
}
if let labelString = labelAttributedString?.string, !labelString.isEmpty {
combinedValueString.append(labelString)
combinedValueString.append(", \(labelString)")
}
strongSelf.accessibilityValue = combinedValueString

View File

@@ -73,6 +73,8 @@ class ItemListSectionHeaderItemNode: ListViewItemNode {
private let titleNode: TextNode
private let accessoryTextNode: TextNode
private let activateArea: AccessibilityAreaNode
init() {
self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false
@@ -84,12 +86,14 @@ class ItemListSectionHeaderItemNode: ListViewItemNode {
self.accessoryTextNode.contentMode = .left
self.accessoryTextNode.contentsScale = UIScreen.main.scale
super.init(layerBacked: false, dynamicBounce: false)
self.activateArea = AccessibilityAreaNode()
self.activateArea.accessibilityTraits = UIAccessibilityTraitStaticText | UIAccessibilityTraitHeader
self.isAccessibilityElement = true
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.titleNode)
self.addSubnode(self.accessoryTextNode)
self.addSubnode(self.activateArea)
}
func asyncLayout() -> (_ item: ItemListSectionHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
@@ -133,7 +137,8 @@ class ItemListSectionHeaderItemNode: ListViewItemNode {
let _ = titleApply()
let _ = accessoryApply()
strongSelf.accessibilityLabel = item.text
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height))
strongSelf.activateArea.accessibilityLabel = item.text
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 7.0), size: titleLayout.size)
strongSelf.accessoryTextNode.frame = CGRect(origin: CGPoint(x: params.width - leftInset - accessoryLayout.size.width, y: 7.0), size: accessoryLayout.size)

View File

@@ -81,6 +81,9 @@ private protocol ItemListSwitchNodeImpl {
var handleColor: UIColor { get set }
var positiveContentColor: UIColor { get set }
var negativeContentColor: UIColor { get set }
var isOn: Bool { get }
func setOn(_ value: Bool, animated: Bool)
}
extension SwitchNode: ItemListSwitchNodeImpl {
@@ -114,6 +117,8 @@ class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
private let switchGestureNode: ASDisplayNode
private var disabledOverlayNode: ASDisplayNode?
private let activateArea: AccessibilityAreaNode
private var item: ItemListSwitchItem?
var tag: ItemListItemTag? {
@@ -145,13 +150,26 @@ class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
self.switchGestureNode = ASDisplayNode()
super.init(layerBacked: false, dynamicBounce: false)
self.activateArea = AccessibilityAreaNode()
self.isAccessibilityElement = true
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.titleNode)
self.addSubnode(self.switchNode)
self.addSubnode(self.switchGestureNode)
self.addSubnode(self.activateArea)
self.activateArea.activate = { [weak self] in
guard let strongSelf = self, let item = strongSelf.item, item.enabled else {
return false
}
let value = !strongSelf.switchNode.isOn
if item.enableInteractiveChanges {
strongSelf.switchNode.setOn(value, animated: true)
}
item.updated(value)
return true
}
}
override func didLoad() {
@@ -213,14 +231,17 @@ class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
if let strongSelf = self {
strongSelf.item = item
strongSelf.accessibilityLabel = item.title
strongSelf.accessibilityValue = item.value ? "On" : "Off"
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height))
strongSelf.activateArea.accessibilityLabel = item.title
strongSelf.activateArea.accessibilityValue = item.value ? "On" : "Off"
strongSelf.activateArea.accessibilityHint = "Tap to change"
var accessibilityTraits = UIAccessibilityTraits()
if item.enabled {
} else {
accessibilityTraits |= UIAccessibilityTraitNotEnabled
}
strongSelf.accessibilityTraits = accessibilityTraits
strongSelf.activateArea.accessibilityTraits = accessibilityTraits
let transition: ContainedViewLayoutTransition
if animated {

View File

@@ -70,6 +70,7 @@ private let titleBoldFont = Font.semibold(14.0)
class ItemListTextItemNode: ListViewItemNode {
private let titleNode: TextNode
private let activateArea: AccessibilityAreaNode
private var item: ItemListTextItem?
@@ -79,12 +80,13 @@ class ItemListTextItemNode: ListViewItemNode {
self.titleNode.contentMode = .left
self.titleNode.contentsScale = UIScreen.main.scale
self.activateArea = AccessibilityAreaNode()
self.activateArea.accessibilityTraits = UIAccessibilityTraitStaticText
super.init(layerBacked: false, dynamicBounce: false)
self.isAccessibilityElement = true
self.accessibilityTraits = UIAccessibilityTraitStaticText
self.addSubnode(self.titleNode)
self.addSubnode(self.activateArea)
}
override func didLoad() {
@@ -126,6 +128,9 @@ class ItemListTextItemNode: ListViewItemNode {
if let strongSelf = self {
strongSelf.item = item
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height))
strongSelf.activateArea.accessibilityLabel = attributedText.string
strongSelf.accessibilityLabel = attributedText.string
let _ = titleApply()