mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 14:45:21 +00:00
Accessibility updates
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user