Improve VoiceOver

This commit is contained in:
Peter 2019-08-02 20:07:20 +03:00
parent ac39dae178
commit ad1dad4b58
59 changed files with 4327 additions and 3772 deletions

View File

@ -79,7 +79,6 @@
buildConfiguration = "DebugHockeyapp" buildConfiguration = "DebugHockeyapp"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
enableAddressSanitizer = "YES"
enableASanStackUseAfterReturn = "YES" enableASanStackUseAfterReturn = "YES"
launchStyle = "0" launchStyle = "0"
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"

View File

@ -4504,3 +4504,89 @@ Any member of this group will be able to see messages in the channel.";
"Channel.AdminLog.MessageRankName" = "changed custom title for %1$@:\n%2$@"; "Channel.AdminLog.MessageRankName" = "changed custom title for %1$@:\n%2$@";
"Channel.AdminLog.MessageRankUsername" = "changed custom title for %1$@ (%2$@):\n%3$@"; "Channel.AdminLog.MessageRankUsername" = "changed custom title for %1$@ (%2$@):\n%3$@";
"Channel.AdminLog.MessageRank" = "changed custom title:\n%1$@"; "Channel.AdminLog.MessageRank" = "changed custom title:\n%1$@";
"VoiceOver.Editing.ClearText" = "Clear text";
"VoiceOver.Recording.StopAndPreview" = "Stop and preview";
"VoiceOver.Media.PlaybackRate" = "Playback rate";
"VoiceOver.Media.PlaybackRateNormal" = "Normal";
"VoiceOver.Media.PlaybackRateFast" = "Fast";
"VoiceOver.Media.PlaybackRateChange" = "Double tap to change";
"VoiceOver.Media.PlaybackStop" = "Stop playback";
"VoiceOver.Media.PlaybackPlay" = "Play";
"VoiceOver.Media.PlaybackPause" = "Pause";
"VoiceOver.Navigation.Compose" = "Compose";
"VoiceOver.Navigation.Search" = "Search";
"VoiceOver.Navigation.ProxySettings" = "Proxy settings";
"VoiceOver.DiscardPreparedContent" = "Discard";
"VoiceOver.AttachMedia" = "Send media";
"VoiceOver.Chat.RecordPreviewVoiceMessage" = "Preview voice message";
"VoiceOver.Chat.RecordModeVoiceMessage" = "Voice message";
"VoiceOver.Chat.RecordModeVoiceMessageInfo" = "Double tap and hold to record voice message. Slide up to pin recording, slide left to cancel. Double tap to switch to video.";
"VoiceOver.Chat.RecordModeVideoMessage" = "Video message";
"VoiceOver.Chat.RecordModeVideoMessageInfo" = "Double tap and hold to record voice message. Slide up to pin recording, slide left to cancel. Double tap to switch to audio.";
"VoiceOver.Chat.Message" = "Message";
"VoiceOver.Chat.YourMessage" = "Your message";
"VoiceOver.Chat.ReplyFrom" = "Reply to message from: %@";
"VoiceOver.Chat.Reply" = "Reply to message";
"VoiceOver.Chat.ReplyToYourMessage" = "Reply to your message";
"VoiceOver.Chat.ForwardedFrom" = "Forwarded from: %@";
"VoiceOver.Chat.ForwardedFromYou" = "Forwarded from you";
"VoiceOver.Chat.PhotoFrom" = "Photo, from: %@";
"VoiceOver.Chat.Photo" = "Photo";
"VoiceOver.Chat.YourPhoto" = "Your photo";
"VoiceOver.Chat.VoiceMessageFrom" = "Voice message, from: %@";
"VoiceOver.Chat.VoiceMessage" = "Voice message";
"VoiceOver.Chat.YourVoiceMessage" = "Your voice message";
"VoiceOver.Chat.MusicFrom" = "Music file, from: %@";
"VoiceOver.Chat.Music" = "Music message";
"VoiceOver.Chat.YourMusic" = "Your music message";
"VoiceOver.Chat.VideoFrom" = "Video, from: %@";
"VoiceOver.Chat.Video" = "Video";
"VoiceOver.Chat.YourVideo" = "Your video";
"VoiceOver.Chat.VideoMessageFrom" = "Video message, from: %@";
"VoiceOver.Chat.VideoMessage" = "Video message";
"VoiceOver.Chat.YourVideoMessage" = "Your video message";
"VoiceOver.Chat.FileFrom" = "File, from: %@";
"VoiceOver.Chat.File" = "File";
"VoiceOver.Chat.YourFile" = "Your file";
"VoiceOver.Chat.ContactFrom" = "Shared contact, from: %@";
"VoiceOver.Chat.Contact" = "Shared contact";
"VoiceOver.Chat.ContactPhoneNumberCount_1" = "%@ phone number";
"VoiceOver.Chat.ContactPhoneNumberCount_any" = "%@ phone numbers";
"VoiceOver.Chat.ContactPhoneNumber" = "Phone number";
"VoiceOver.Chat.ContactEmailCount_1" = "%@ email address";
"VoiceOver.Chat.ContactEmailCount_any" = "%@ email addresses";
"VoiceOver.Chat.ContactEmail" = "Email";
"VoiceOver.Chat.ContactOrganization" = "Organization: %@";
"VoiceOver.Chat.YourContact" = "Your shared contact";
"VoiceOver.Chat.AnonymousPollFrom" = "Anonymous poll, from: %@";
"VoiceOver.Chat.AnonymousPoll" = "Anonymous poll";
"VoiceOver.Chat.YourAnonymousPoll" = "Your Anonymous poll";
"VoiceOver.Chat.PollOptionCount_1" = "%@ option:";
"VoiceOver.Chat.PollOptionCount_any" = "%@ options:";
"VoiceOver.Chat.PollVotes_1" = "%@ vote";
"VoiceOver.Chat.PollVotes_any" = "%@ votes";
"VoiceOver.Chat.PollNoVotes" = "No votes";
"VoiceOver.Chat.PollFinalResults" = "Final results";
"VoiceOver.Chat.OptionSelected" = "selected";
"VoiceOver.Chat.PagePreview" = "Page preview";
"VoiceOver.Chat.Title" = "Title: %@";
"VoiceOver.Chat.Caption" = "Caption: %@";
"VoiceOver.Chat.Duration" = "Duration: %@";
"VoiceOver.Chat.Size" = "Size %@";
"VoiceOver.Chat.MusicTitle" = "%1$@, by %2$@";
"VoiceOver.Chat.PlayHint" = "Double tap to play";
"VoiceOver.Chat.OpenHint" = "Double tap to open";
"VoiceOver.Chat.OpenLinkHint" = "Double tap to open link";
"VoiceOver.Chat.SeenByRecipient" = "Seen by recipient";
"VoiceOver.Chat.SeenByRecipients" = "Seen by recipients";
"VoiceOver.Chat.Selected" = "Selected";
"VoiceOver.MessageContextDelete" = "Delete";
"VoiceOver.MessageContextReport" = "Report";
"VoiceOver.MessageContextForward" = "Forward";
"VoiceOver.MessageContextShare" = "Share";
"VoiceOver.MessageContextSend" = "Send";
"VoiceOver.MessageContextReply" = "Reply";
"VoiceOver.MessageContextOpenMessageMenu" = "Open message menu";
"ProxyServer.VoiceOver.Active" = "Active";

View File

@ -4,6 +4,7 @@ import AsyncDisplayKit
public final class AccessibilityAreaNode: ASDisplayNode { public final class AccessibilityAreaNode: ASDisplayNode {
public var activate: (() -> Bool)? public var activate: (() -> Bool)?
public var focused: (() -> Void)?
override public init() { override public init() {
super.init() super.init()
@ -18,4 +19,24 @@ public final class AccessibilityAreaNode: ASDisplayNode {
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return nil return nil
} }
override public func accessibilityElementDidBecomeFocused() {
if let focused = self.focused {
focused()
} else {
var supernode = self.supernode
while true {
if let supernodeValue = supernode {
if let listItemNode = supernodeValue as? ListViewItemNode {
listItemNode.accessibilityElementDidBecomeFocused()
break
} else {
supernode = supernodeValue.supernode
}
} else {
break
}
}
}
}
} }

View File

@ -55,11 +55,13 @@ public class ActionSheetButtonNode: ActionSheetItemNode {
private let button: HighlightTrackingButton private let button: HighlightTrackingButton
private let label: ASTextNode private let label: ASTextNode
private let accessibilityArea: AccessibilityAreaNode
override public init(theme: ActionSheetControllerTheme) { override public init(theme: ActionSheetControllerTheme) {
self.theme = theme self.theme = theme
self.button = HighlightTrackingButton() self.button = HighlightTrackingButton()
self.button.isAccessibilityElement = false
self.label = ASTextNode() self.label = ASTextNode()
self.label.isUserInteractionEnabled = false self.label.isUserInteractionEnabled = false
@ -67,6 +69,8 @@ public class ActionSheetButtonNode: ActionSheetItemNode {
self.label.displaysAsynchronously = false self.label.displaysAsynchronously = false
self.label.truncationMode = .byTruncatingTail self.label.truncationMode = .byTruncatingTail
self.accessibilityArea = AccessibilityAreaNode()
super.init(theme: theme) super.init(theme: theme)
self.view.addSubview(self.button) self.view.addSubview(self.button)
@ -74,6 +78,8 @@ public class ActionSheetButtonNode: ActionSheetItemNode {
self.label.isUserInteractionEnabled = false self.label.isUserInteractionEnabled = false
self.addSubnode(self.label) self.addSubnode(self.label)
self.addSubnode(self.accessibilityArea)
self.button.highligthedChanged = { [weak self] highlighted in self.button.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self { if let strongSelf = self {
if highlighted { if highlighted {
@ -87,6 +93,10 @@ public class ActionSheetButtonNode: ActionSheetItemNode {
} }
self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside) self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
self.accessibilityArea.activate = { [weak self] in
self?.buttonPressed()
return true
}
} }
func setItem(_ item: ActionSheetButtonItem) { func setItem(_ item: ActionSheetButtonItem) {
@ -109,9 +119,18 @@ public class ActionSheetButtonNode: ActionSheetItemNode {
textFont = ActionSheetButtonNode.boldFont textFont = ActionSheetButtonNode.boldFont
} }
self.label.attributedText = NSAttributedString(string: item.title, font: textFont, textColor: textColor) self.label.attributedText = NSAttributedString(string: item.title, font: textFont, textColor: textColor)
self.label.isAccessibilityElement = false
self.button.isEnabled = item.enabled self.button.isEnabled = item.enabled
self.accessibilityArea.accessibilityLabel = item.title
var accessibilityTraits: UIAccessibilityTraits = [.button]
if !item.enabled {
accessibilityTraits.insert(.notEnabled)
}
self.accessibilityArea.accessibilityTraits = accessibilityTraits
self.setNeedsLayout() self.setNeedsLayout()
} }
@ -128,6 +147,7 @@ public class ActionSheetButtonNode: ActionSheetItemNode {
let labelSize = self.label.measure(CGSize(width: max(1.0, size.width - 10.0), height: size.height)) let labelSize = self.label.measure(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.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)
} }
@objc func buttonPressed() { @objc func buttonPressed() {

View File

@ -50,20 +50,25 @@ public class ActionSheetCheckboxItemNode: ActionSheetItemNode {
private let labelNode: ImmediateTextNode private let labelNode: ImmediateTextNode
private let checkNode: ASImageNode private let checkNode: ASImageNode
private let accessibilityArea: AccessibilityAreaNode
override public init(theme: ActionSheetControllerTheme) { override public init(theme: ActionSheetControllerTheme) {
self.theme = theme self.theme = theme
self.button = HighlightTrackingButton() self.button = HighlightTrackingButton()
self.button.isAccessibilityElement = false
self.titleNode = ImmediateTextNode() self.titleNode = ImmediateTextNode()
self.titleNode.maximumNumberOfLines = 1 self.titleNode.maximumNumberOfLines = 1
self.titleNode.isUserInteractionEnabled = false self.titleNode.isUserInteractionEnabled = false
self.titleNode.displaysAsynchronously = false self.titleNode.displaysAsynchronously = false
self.titleNode.isAccessibilityElement = false
self.labelNode = ImmediateTextNode() self.labelNode = ImmediateTextNode()
self.labelNode.maximumNumberOfLines = 1 self.labelNode.maximumNumberOfLines = 1
self.labelNode.isUserInteractionEnabled = false self.labelNode.isUserInteractionEnabled = false
self.labelNode.displaysAsynchronously = false self.labelNode.displaysAsynchronously = false
self.labelNode.isAccessibilityElement = false
self.checkNode = ASImageNode() self.checkNode = ASImageNode()
self.checkNode.isUserInteractionEnabled = false self.checkNode.isUserInteractionEnabled = false
@ -78,6 +83,9 @@ public class ActionSheetCheckboxItemNode: ActionSheetItemNode {
context.addLine(to: CGPoint(x: 1.0, y: 5.81145833)) context.addLine(to: CGPoint(x: 1.0, y: 5.81145833))
context.strokePath() context.strokePath()
}) })
self.checkNode.isAccessibilityElement = false
self.accessibilityArea = AccessibilityAreaNode()
super.init(theme: theme) super.init(theme: theme)
@ -85,6 +93,7 @@ public class ActionSheetCheckboxItemNode: ActionSheetItemNode {
self.addSubnode(self.titleNode) self.addSubnode(self.titleNode)
self.addSubnode(self.labelNode) self.addSubnode(self.labelNode)
self.addSubnode(self.checkNode) self.addSubnode(self.checkNode)
self.addSubnode(self.accessibilityArea)
self.button.highligthedChanged = { [weak self] highlighted in self.button.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self { if let strongSelf = self {
@ -98,6 +107,11 @@ public class ActionSheetCheckboxItemNode: ActionSheetItemNode {
} }
} }
self.accessibilityArea.activate = { [weak self] in
self?.buttonPressed()
return true
}
self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside) self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
} }
@ -108,6 +122,14 @@ public class ActionSheetCheckboxItemNode: ActionSheetItemNode {
self.labelNode.attributedText = NSAttributedString(string: item.label, font: ActionSheetCheckboxItemNode.defaultFont, textColor: self.theme.secondaryTextColor) self.labelNode.attributedText = NSAttributedString(string: item.label, font: ActionSheetCheckboxItemNode.defaultFont, textColor: self.theme.secondaryTextColor)
self.checkNode.isHidden = !item.value self.checkNode.isHidden = !item.value
self.accessibilityArea.accessibilityLabel = item.title
var accessibilityTraits: UIAccessibilityTraits = [.button]
if item.value {
accessibilityTraits.insert(.selected)
}
self.accessibilityArea.accessibilityTraits = accessibilityTraits
self.setNeedsLayout() self.setNeedsLayout()
} }
@ -137,6 +159,8 @@ public class ActionSheetCheckboxItemNode: ActionSheetItemNode {
if let image = self.checkNode.image { if let image = self.checkNode.image {
self.checkNode.frame = CGRect(origin: CGPoint(x: floor(checkOrigin - (image.size.width / 2.0)), y: floor((size.height - image.size.height) / 2.0)), size: image.size) self.checkNode.frame = CGRect(origin: CGPoint(x: floor(checkOrigin - (image.size.width / 2.0)), y: floor((size.height - image.size.height) / 2.0)), size: image.size)
} }
self.accessibilityArea.frame = CGRect(origin: CGPoint(), size: size)
} }
@objc func buttonPressed() { @objc func buttonPressed() {

View File

@ -3,12 +3,6 @@ import AsyncDisplayKit
private let containerInsets = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0) private let containerInsets = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0)
private class ActionSheetControllerNodeScrollView: UIScrollView {
override func touchesShouldCancel(in view: UIView) -> Bool {
return true
}
}
final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate {
var theme: ActionSheetControllerTheme { var theme: ActionSheetControllerTheme {
didSet { didSet {
@ -26,6 +20,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate {
private let itemGroupsContainerNode: ActionSheetItemGroupsContainerNode private let itemGroupsContainerNode: ActionSheetItemGroupsContainerNode
private let scrollNode: ASScrollNode
private let scrollView: UIScrollView private let scrollView: UIScrollView
var dismiss: (Bool) -> Void = { _ in } var dismiss: (Bool) -> Void = { _ in }
@ -35,7 +30,9 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate {
init(theme: ActionSheetControllerTheme) { init(theme: ActionSheetControllerTheme) {
self.theme = theme self.theme = theme
self.scrollView = ActionSheetControllerNodeScrollView() self.scrollNode = ASScrollNode()
self.scrollNode.canCancelAllTouchesInViews = true
self.scrollView = self.scrollNode.view
if #available(iOSApplicationExtension 11.0, *) { if #available(iOSApplicationExtension 11.0, *) {
self.scrollView.contentInsetAdjustmentBehavior = .never self.scrollView.contentInsetAdjustmentBehavior = .never
@ -64,7 +61,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.scrollView.delegate = self self.scrollView.delegate = self
self.view.addSubview(self.scrollView) self.addSubnode(self.scrollNode)
self.scrollView.addSubview(self.dismissTapView) self.scrollView.addSubview(self.dismissTapView)
@ -75,7 +72,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.dismissTapView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimNodeTap(_:)))) self.dismissTapView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimNodeTap(_:))))
self.scrollView.addSubnode(self.itemGroupsContainerNode) self.scrollNode.addSubnode(self.itemGroupsContainerNode)
self.updateTheme() self.updateTheme()
} }

View File

@ -1,12 +1,6 @@
import UIKit import UIKit
import AsyncDisplayKit import AsyncDisplayKit
private class ActionSheetItemGroupNodeScrollView: UIScrollView {
override func touchesShouldCancel(in view: UIView) -> Bool {
return true
}
}
final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate { final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate {
private let theme: ActionSheetControllerTheme private let theme: ActionSheetControllerTheme
@ -17,7 +11,7 @@ final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate {
private let clippingNode: ASDisplayNode private let clippingNode: ASDisplayNode
private let backgroundEffectView: UIVisualEffectView private let backgroundEffectView: UIVisualEffectView
private let scrollView: UIScrollView private let scrollNode: ASScrollNode
private var itemNodes: [ActionSheetItemNode] = [] private var itemNodes: [ActionSheetItemNode] = []
private var leadingVisibleNodeCount: CGFloat = 100.0 private var leadingVisibleNodeCount: CGFloat = 100.0
@ -47,14 +41,15 @@ final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate {
self.backgroundEffectView = UIVisualEffectView(effect: UIBlurEffect(style: self.theme.backgroundType == .light ? .light : .dark)) self.backgroundEffectView = UIVisualEffectView(effect: UIBlurEffect(style: self.theme.backgroundType == .light ? .light : .dark))
self.scrollView = ActionSheetItemGroupNodeScrollView() self.scrollNode = ASScrollNode()
self.scrollNode.canCancelAllTouchesInViews = true
if #available(iOSApplicationExtension 11.0, *) { if #available(iOSApplicationExtension 11.0, *) {
self.scrollView.contentInsetAdjustmentBehavior = .never self.scrollNode.view.contentInsetAdjustmentBehavior = .never
} }
self.scrollView.delaysContentTouches = false self.scrollNode.view.delaysContentTouches = false
self.scrollView.canCancelContentTouches = true self.scrollNode.view.canCancelContentTouches = true
self.scrollView.showsVerticalScrollIndicator = false self.scrollNode.view.showsVerticalScrollIndicator = false
self.scrollView.showsHorizontalScrollIndicator = false self.scrollNode.view.showsHorizontalScrollIndicator = false
super.init() super.init()
@ -63,10 +58,10 @@ final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate {
self.view.addSubview(self.bottomDimView) self.view.addSubview(self.bottomDimView)
self.view.addSubview(self.trailingDimView) self.view.addSubview(self.trailingDimView)
self.scrollView.delegate = self self.scrollNode.view.delegate = self
self.clippingNode.view.addSubview(self.backgroundEffectView) self.clippingNode.view.addSubview(self.backgroundEffectView)
self.clippingNode.view.addSubview(self.scrollView) self.clippingNode.addSubnode(self.scrollNode)
self.addSubnode(self.clippingNode) self.addSubnode(self.clippingNode)
} }
@ -80,7 +75,7 @@ final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate {
for node in nodes { for node in nodes {
if !self.itemNodes.contains(where: { $0 === node }) { if !self.itemNodes.contains(where: { $0 === node }) {
self.scrollView.addSubnode(node) self.scrollNode.addSubnode(node)
} }
} }
@ -117,8 +112,8 @@ final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate {
override func layout() { override func layout() {
let scrollViewFrame = CGRect(origin: CGPoint(), size: self.calculatedSize) let scrollViewFrame = CGRect(origin: CGPoint(), size: self.calculatedSize)
var updateOffset = false var updateOffset = false
if !self.scrollView.frame.equalTo(scrollViewFrame) { if !self.scrollNode.frame.equalTo(scrollViewFrame) {
self.scrollView.frame = scrollViewFrame self.scrollNode.frame = scrollViewFrame
updateOffset = true updateOffset = true
} }
@ -149,17 +144,17 @@ final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate {
} }
let scrollViewContentSize = CGSize(width: self.calculatedSize.width, height: itemNodesHeight) let scrollViewContentSize = CGSize(width: self.calculatedSize.width, height: itemNodesHeight)
if !self.scrollView.contentSize.equalTo(scrollViewContentSize) { if !self.scrollNode.view.contentSize.equalTo(scrollViewContentSize) {
self.scrollView.contentSize = scrollViewContentSize self.scrollNode.view.contentSize = scrollViewContentSize
} }
let scrollViewContentInsets = UIEdgeInsets(top: max(0.0, self.calculatedSize.height - leadingVisibleNodeSize), left: 0.0, bottom: 0.0, right: 0.0) let scrollViewContentInsets = UIEdgeInsets(top: max(0.0, self.calculatedSize.height - leadingVisibleNodeSize), left: 0.0, bottom: 0.0, right: 0.0)
if !UIEdgeInsetsEqualToEdgeInsets(self.scrollView.contentInset, scrollViewContentInsets) { if self.scrollNode.view.contentInset != scrollViewContentInsets {
self.scrollView.contentInset = scrollViewContentInsets self.scrollNode.view.contentInset = scrollViewContentInsets
} }
if updateOffset { if updateOffset {
self.scrollView.contentOffset = CGPoint(x: 0.0, y: -scrollViewContentInsets.top) self.scrollNode.view.contentOffset = CGPoint(x: 0.0, y: -scrollViewContentInsets.top)
} }
self.updateOverscroll() self.updateOverscroll()
@ -167,20 +162,20 @@ final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate {
private func currentVerticalOverscroll() -> CGFloat { private func currentVerticalOverscroll() -> CGFloat {
var verticalOverscroll: CGFloat = 0.0 var verticalOverscroll: CGFloat = 0.0
if scrollView.contentOffset.y < 0.0 { if scrollNode.view.contentOffset.y < 0.0 {
verticalOverscroll = scrollView.contentOffset.y verticalOverscroll = scrollNode.view.contentOffset.y
} else if scrollView.contentOffset.y > scrollView.contentSize.height - scrollView.bounds.size.height { } else if scrollNode.view.contentOffset.y > scrollNode.view.contentSize.height - scrollNode.view.bounds.size.height {
verticalOverscroll = scrollView.contentOffset.y - (scrollView.contentSize.height - scrollView.bounds.size.height) verticalOverscroll = scrollNode.view.contentOffset.y - (scrollNode.view.contentSize.height - scrollNode.view.bounds.size.height)
} }
return verticalOverscroll return verticalOverscroll
} }
private func currentRealVerticalOverscroll() -> CGFloat { private func currentRealVerticalOverscroll() -> CGFloat {
var verticalOverscroll: CGFloat = 0.0 var verticalOverscroll: CGFloat = 0.0
if scrollView.contentOffset.y < 0.0 { if scrollNode.view.contentOffset.y < 0.0 {
verticalOverscroll = scrollView.contentOffset.y verticalOverscroll = scrollNode.view.contentOffset.y
} else if scrollView.contentOffset.y > scrollView.contentSize.height - scrollView.bounds.size.height { } else if scrollNode.view.contentOffset.y > scrollNode.view.contentSize.height - scrollNode.view.bounds.size.height {
verticalOverscroll = scrollView.contentOffset.y - (scrollView.contentSize.height - scrollView.bounds.size.height) verticalOverscroll = scrollNode.view.contentOffset.y - (scrollNode.view.contentSize.height - scrollNode.view.bounds.size.height)
} }
return verticalOverscroll return verticalOverscroll
} }

View File

@ -38,21 +38,28 @@ public class ActionSheetSwitchNode: ActionSheetItemNode {
private let label: ASTextNode private let label: ASTextNode
private let switchNode: SwitchNode private let switchNode: SwitchNode
private let accessibilityArea: AccessibilityAreaNode
override public init(theme: ActionSheetControllerTheme) { override public init(theme: ActionSheetControllerTheme) {
self.theme = theme self.theme = theme
self.button = HighlightTrackingButton() self.button = HighlightTrackingButton()
self.button.isAccessibilityElement = false
self.label = ASTextNode() self.label = ASTextNode()
self.label.isUserInteractionEnabled = false self.label.isUserInteractionEnabled = false
self.label.maximumNumberOfLines = 1 self.label.maximumNumberOfLines = 1
self.label.displaysAsynchronously = false self.label.displaysAsynchronously = false
self.label.truncationMode = .byTruncatingTail self.label.truncationMode = .byTruncatingTail
self.label.isAccessibilityElement = false
self.switchNode = SwitchNode() self.switchNode = SwitchNode()
self.switchNode.frameColor = theme.switchFrameColor self.switchNode.frameColor = theme.switchFrameColor
self.switchNode.contentColor = theme.switchContentColor self.switchNode.contentColor = theme.switchContentColor
self.switchNode.handleColor = theme.switchHandleColor self.switchNode.handleColor = theme.switchHandleColor
self.switchNode.isAccessibilityElement = false
self.accessibilityArea = AccessibilityAreaNode()
super.init(theme: theme) super.init(theme: theme)
@ -62,19 +69,35 @@ public class ActionSheetSwitchNode: ActionSheetItemNode {
self.addSubnode(self.label) self.addSubnode(self.label)
self.addSubnode(self.switchNode) self.addSubnode(self.switchNode)
self.addSubnode(self.accessibilityArea)
self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside) self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
self.switchNode.valueUpdated = { [weak self] value in self.switchNode.valueUpdated = { [weak self] value in
self?.item?.action(value) self?.item?.action(value)
} }
self.accessibilityArea.activate = { [weak self] in
self?.buttonPressed()
return true
}
} }
func setItem(_ item: ActionSheetSwitchItem) { func setItem(_ item: ActionSheetSwitchItem) {
self.item = item self.item = item
self.label.attributedText = NSAttributedString(string: item.title, font: ActionSheetButtonNode.defaultFont, textColor: self.theme.primaryTextColor) self.label.attributedText = NSAttributedString(string: item.title, font: ActionSheetButtonNode.defaultFont, textColor: self.theme.primaryTextColor)
self.label.isAccessibilityElement = false
self.switchNode.isOn = item.isOn self.switchNode.isOn = item.isOn
self.accessibilityArea.accessibilityLabel = item.title
var accessibilityTraits: UIAccessibilityTraits = [.button]
if item.isOn {
accessibilityTraits.insert(.selected)
}
self.accessibilityArea.accessibilityTraits = accessibilityTraits
self.setNeedsLayout() self.setNeedsLayout()
} }
@ -94,6 +117,8 @@ public class ActionSheetSwitchNode: ActionSheetItemNode {
let switchSize = CGSize(width: 51.0, height: 31.0) let switchSize = CGSize(width: 51.0, height: 31.0)
self.switchNode.frame = CGRect(origin: CGPoint(x: size.width - 16.0 - switchSize.width, y: floor((size.height - switchSize.height) / 2.0)), size: switchSize) self.switchNode.frame = CGRect(origin: CGPoint(x: size.width - 16.0 - switchSize.width, y: floor((size.height - switchSize.height) / 2.0)), size: switchSize)
self.accessibilityArea.frame = CGRect(origin: CGPoint(), size: size)
} }
@objc func buttonPressed() { @objc func buttonPressed() {

View File

@ -34,6 +34,8 @@ public class ActionSheetTextNode: ActionSheetItemNode {
private let label: ASTextNode private let label: ASTextNode
private let accessibilityArea: AccessibilityAreaNode
override public init(theme: ActionSheetControllerTheme) { override public init(theme: ActionSheetControllerTheme) {
self.theme = theme self.theme = theme
@ -42,17 +44,24 @@ public class ActionSheetTextNode: ActionSheetItemNode {
self.label.maximumNumberOfLines = 0 self.label.maximumNumberOfLines = 0
self.label.displaysAsynchronously = false self.label.displaysAsynchronously = false
self.label.truncationMode = .byTruncatingTail self.label.truncationMode = .byTruncatingTail
self.label.isAccessibilityElement = false
self.accessibilityArea = AccessibilityAreaNode()
self.accessibilityArea.accessibilityTraits = .staticText
super.init(theme: theme) super.init(theme: theme)
self.label.isUserInteractionEnabled = false self.label.isUserInteractionEnabled = false
self.addSubnode(self.label) self.addSubnode(self.label)
self.addSubnode(self.accessibilityArea)
} }
func setItem(_ item: ActionSheetTextItem) { func setItem(_ item: ActionSheetTextItem) {
self.item = item self.item = item
self.label.attributedText = NSAttributedString(string: item.title, font: ActionSheetTextNode.defaultFont, textColor: self.theme.secondaryTextColor, paragraphAlignment: .center) self.label.attributedText = NSAttributedString(string: item.title, font: ActionSheetTextNode.defaultFont, textColor: self.theme.secondaryTextColor, paragraphAlignment: .center)
self.accessibilityArea.accessibilityLabel = item.title
self.setNeedsLayout() self.setNeedsLayout()
} }
@ -69,5 +78,7 @@ public class ActionSheetTextNode: ActionSheetItemNode {
let labelSize = self.label.measure(CGSize(width: max(1.0, size.width - 20.0), height: size.height)) let labelSize = self.label.measure(CGSize(width: max(1.0, size.width - 20.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.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)
} }
} }

View File

@ -1,5 +1,7 @@
#import "TGMediaAssetsPhotoCell.h" #import "TGMediaAssetsPhotoCell.h"
#import "LegacyComponentsInternal.h"
NSString *const TGMediaAssetsPhotoCellKind = @"TGMediaAssetsPhotoCellKind"; NSString *const TGMediaAssetsPhotoCellKind = @"TGMediaAssetsPhotoCellKind";
@implementation TGMediaAssetsPhotoCell @implementation TGMediaAssetsPhotoCell
@ -7,7 +9,7 @@ NSString *const TGMediaAssetsPhotoCellKind = @"TGMediaAssetsPhotoCellKind";
- (instancetype)initWithFrame:(CGRect)frame { - (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame]; self = [super initWithFrame:frame];
if (self != nil) { if (self != nil) {
self.accessibilityLabel = @"Photo"; self.accessibilityLabel = TGLocalized(@"Message.Photo");
} }
return self; return self;
} }

View File

@ -86,7 +86,7 @@ NSString *const TGMediaAssetsVideoCellKind = @"TGMediaAssetsVideoCellKind";
_durationLabel.accessibilityIgnoresInvertColors = true; _durationLabel.accessibilityIgnoresInvertColors = true;
} }
self.accessibilityLabel = @"Video"; self.accessibilityLabel = TGLocalized(@"Message.Video");
} }
return self; return self;
} }

View File

@ -394,7 +394,7 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius
[[_presentation view] addSubview:_innerIconWrapperView]; [[_presentation view] addSubview:_innerIconWrapperView];
_stopButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 38.0f, 38.0f)]; _stopButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 38.0f, 38.0f)];
_stopButton.accessibilityLabel = @"Stop and preview"; _stopButton.accessibilityLabel = TGLocalized(@"VoiceOver.Recording.StopAndPreview");
_stopButton.adjustsImageWhenHighlighted = false; _stopButton.adjustsImageWhenHighlighted = false;
_stopButton.exclusiveTouch = true; _stopButton.exclusiveTouch = true;
[_stopButton setImage:[self stopButtonImage] forState:UIControlStateNormal]; [_stopButton setImage:[self stopButtonImage] forState:UIControlStateNormal];

View File

@ -55,23 +55,31 @@ public class ActionSheetPeerItemNode: ActionSheetItemNode {
private let label: ImmediateTextNode private let label: ImmediateTextNode
private let checkNode: ASImageNode private let checkNode: ASImageNode
private let accessibilityArea: AccessibilityAreaNode
override public init(theme: ActionSheetControllerTheme) { override public init(theme: ActionSheetControllerTheme) {
self.theme = theme self.theme = theme
self.button = HighlightTrackingButton() self.button = HighlightTrackingButton()
self.button.isAccessibilityElement = false
self.avatarNode = AvatarNode(font: avatarFont) self.avatarNode = AvatarNode(font: avatarFont)
self.avatarNode.isLayerBacked = !smartInvertColorsEnabled() self.avatarNode.isLayerBacked = !smartInvertColorsEnabled()
self.avatarNode.isAccessibilityElement = false
self.label = ImmediateTextNode() self.label = ImmediateTextNode()
self.label.isUserInteractionEnabled = false self.label.isUserInteractionEnabled = false
self.label.displaysAsynchronously = false self.label.displaysAsynchronously = false
self.label.maximumNumberOfLines = 1 self.label.maximumNumberOfLines = 1
self.label.isAccessibilityElement = false
self.checkNode = ASImageNode() self.checkNode = ASImageNode()
self.checkNode.displaysAsynchronously = false self.checkNode.displaysAsynchronously = false
self.checkNode.displayWithoutProcessing = true self.checkNode.displayWithoutProcessing = true
self.checkNode.image = generateItemListCheckIcon(color: theme.primaryTextColor) self.checkNode.image = generateItemListCheckIcon(color: theme.primaryTextColor)
self.checkNode.isAccessibilityElement = false
self.accessibilityArea = AccessibilityAreaNode()
super.init(theme: theme) super.init(theme: theme)
@ -79,6 +87,7 @@ public class ActionSheetPeerItemNode: ActionSheetItemNode {
self.addSubnode(self.avatarNode) self.addSubnode(self.avatarNode)
self.addSubnode(self.label) self.addSubnode(self.label)
self.addSubnode(self.checkNode) self.addSubnode(self.checkNode)
self.addSubnode(self.accessibilityArea)
self.button.highligthedChanged = { [weak self] highlighted in self.button.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self { if let strongSelf = self {
@ -93,6 +102,11 @@ public class ActionSheetPeerItemNode: ActionSheetItemNode {
} }
self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside) self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
self.accessibilityArea.activate = { [weak self] in
self?.buttonPressed()
return true
}
} }
func setItem(_ item: ActionSheetPeerItem) { func setItem(_ item: ActionSheetPeerItem) {
@ -105,6 +119,13 @@ public class ActionSheetPeerItemNode: ActionSheetItemNode {
self.checkNode.isHidden = !item.isSelected self.checkNode.isHidden = !item.isSelected
var accessibilityTraits: UIAccessibilityTraits = [.button]
if item.isSelected {
accessibilityTraits.insert(.selected)
}
self.accessibilityArea.accessibilityTraits = accessibilityTraits
self.accessibilityArea.accessibilityLabel = item.title
self.setNeedsLayout() self.setNeedsLayout()
} }
@ -130,6 +151,8 @@ public class ActionSheetPeerItemNode: ActionSheetItemNode {
if let image = self.checkNode.image { if let image = self.checkNode.image {
self.checkNode.frame = CGRect(origin: CGPoint(x: size.width - image.size.width - 16.0, y: floor((size.height - image.size.height) / 2.0)), size: image.size) self.checkNode.frame = CGRect(origin: CGPoint(x: size.width - image.size.width - 16.0, y: floor((size.height - image.size.height) / 2.0)), size: image.size)
} }
self.accessibilityArea.frame = CGRect(origin: CGPoint(), size: size)
} }
@objc func buttonPressed() { @objc func buttonPressed() {

View File

@ -50,6 +50,8 @@ public final class CallController: ViewController {
super.init(navigationBarPresentationData: nil) super.init(navigationBarPresentationData: nil)
self.isOpaqueWhenInOverlay = true
self.statusBar.statusBarStyle = .White self.statusBar.statusBarStyle = .White
self.statusBar.ignoreInCall = true self.statusBar.ignoreInCall = true

View File

@ -187,6 +187,8 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
var editableControlNode: ItemListEditableControlNode? var editableControlNode: ItemListEditableControlNode?
private let accessibilityArea: AccessibilityAreaNode
private var avatarState: (Account, Peer?)? private var avatarState: (Account, Peer?)?
private var layoutParams: (CallListCallItem, ListViewItemLayoutParams, Bool, Bool, Bool)? private var layoutParams: (CallListCallItem, ListViewItemLayoutParams, Bool, Bool, Bool)?
@ -218,6 +220,8 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
self.infoButtonNode = HighlightableButtonNode() self.infoButtonNode = HighlightableButtonNode()
self.infoButtonNode.hitTestSlop = UIEdgeInsets(top: -6.0, left: -6.0, bottom: -6.0, right: -10.0) self.infoButtonNode.hitTestSlop = UIEdgeInsets(top: -6.0, left: -6.0, bottom: -6.0, right: -10.0)
self.accessibilityArea = AccessibilityAreaNode()
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
self.addSubnode(self.backgroundNode) self.addSubnode(self.backgroundNode)
@ -227,8 +231,17 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
self.addSubnode(self.statusNode) self.addSubnode(self.statusNode)
self.addSubnode(self.dateNode) self.addSubnode(self.dateNode)
self.addSubnode(self.infoButtonNode) self.addSubnode(self.infoButtonNode)
self.addSubnode(self.accessibilityArea)
self.infoButtonNode.addTarget(self, action: #selector(self.infoPressed), forControlEvents: .touchUpInside) self.infoButtonNode.addTarget(self, action: #selector(self.infoPressed), forControlEvents: .touchUpInside)
self.accessibilityArea.activate = { [weak self] in
guard let item = self?.layoutParams?.0 else {
return false
}
item.interaction.call(item.topMessage.id.peerId)
return true
}
} }
override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
@ -561,6 +574,15 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
strongSelf.updateLayout(size: nodeLayout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset) strongSelf.updateLayout(size: nodeLayout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
strongSelf.accessibilityArea.accessibilityTraits = .button
strongSelf.accessibilityArea.accessibilityLabel = titleAttributedString?.string
strongSelf.accessibilityArea.accessibilityValue = statusAttributedString?.string
strongSelf.accessibilityArea.frame = CGRect(origin: CGPoint(), size: nodeLayout.contentSize)
strongSelf.infoButtonNode.accessibilityLabel = item.strings.Conversation_Info
strongSelf.view.accessibilityCustomActions = [UIAccessibilityCustomAction(name: item.strings.Common_Delete, target: strongSelf, selector: #selector(strongSelf.performLocalAccessibilityCustomAction(_:)))]
strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.strings.Common_Delete, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)])) strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.strings.Common_Delete, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)]))
strongSelf.setRevealOptionsOpened(item.revealed, animated: animated) strongSelf.setRevealOptionsOpened(item.revealed, animated: animated)
} }
@ -658,4 +680,10 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
item.interaction.delete(item.messages.map { $0.id }) item.interaction.delete(item.messages.map { $0.id })
} }
} }
@objc private func performLocalAccessibilityCustomAction(_ action: UIAccessibilityCustomAction) {
if let item = self.layoutParams?.0 {
item.interaction.delete(item.messages.map { $0.id })
}
}
} }

View File

@ -44,21 +44,26 @@ public class CallRouteActionSheetItemNode: ActionSheetItemNode {
private let iconNode: ASImageNode private let iconNode: ASImageNode
private let checkNode: ASImageNode private let checkNode: ASImageNode
private let accessibilityArea: AccessibilityAreaNode
override public init(theme: ActionSheetControllerTheme) { override public init(theme: ActionSheetControllerTheme) {
self.theme = theme self.theme = theme
self.button = HighlightTrackingButton() self.button = HighlightTrackingButton()
self.button.isAccessibilityElement = false
self.label = ASTextNode() self.label = ASTextNode()
self.label.isUserInteractionEnabled = false self.label.isUserInteractionEnabled = false
self.label.maximumNumberOfLines = 1 self.label.maximumNumberOfLines = 1
self.label.displaysAsynchronously = false self.label.displaysAsynchronously = false
self.label.truncationMode = .byTruncatingTail self.label.truncationMode = .byTruncatingTail
self.label.isAccessibilityElement = false
self.iconNode = ASImageNode() self.iconNode = ASImageNode()
self.iconNode.isUserInteractionEnabled = false self.iconNode.isUserInteractionEnabled = false
self.iconNode.displayWithoutProcessing = true self.iconNode.displayWithoutProcessing = true
self.iconNode.displaysAsynchronously = false self.iconNode.displaysAsynchronously = false
self.iconNode.isAccessibilityElement = false
self.checkNode = ASImageNode() self.checkNode = ASImageNode()
self.checkNode.isUserInteractionEnabled = false self.checkNode.isUserInteractionEnabled = false
@ -73,6 +78,9 @@ public class CallRouteActionSheetItemNode: ActionSheetItemNode {
context.addLine(to: CGPoint(x: 1.0, y: 5.81145833)) context.addLine(to: CGPoint(x: 1.0, y: 5.81145833))
context.strokePath() context.strokePath()
}) })
self.checkNode.isAccessibilityElement = false
self.accessibilityArea = AccessibilityAreaNode()
super.init(theme: theme) super.init(theme: theme)
@ -82,6 +90,7 @@ public class CallRouteActionSheetItemNode: ActionSheetItemNode {
self.addSubnode(self.label) self.addSubnode(self.label)
self.addSubnode(self.iconNode) self.addSubnode(self.iconNode)
self.addSubnode(self.checkNode) self.addSubnode(self.checkNode)
self.addSubnode(self.accessibilityArea)
self.button.highligthedChanged = { [weak self] highlighted in self.button.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self { if let strongSelf = self {
@ -96,6 +105,11 @@ public class CallRouteActionSheetItemNode: ActionSheetItemNode {
} }
self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside) self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
self.accessibilityArea.activate = { [weak self] in
self?.buttonPressed()
return true
}
} }
func setItem(_ item: CallRouteActionSheetItem) { func setItem(_ item: CallRouteActionSheetItem) {
@ -109,6 +123,13 @@ public class CallRouteActionSheetItemNode: ActionSheetItemNode {
} }
self.checkNode.isHidden = !item.selected self.checkNode.isHidden = !item.selected
var accessibilityTraits: UIAccessibilityTraits = [.button]
if item.selected {
accessibilityTraits.insert(.selected)
}
self.accessibilityArea.accessibilityTraits = accessibilityTraits
self.accessibilityArea.accessibilityLabel = item.title
self.setNeedsLayout() self.setNeedsLayout()
} }

View File

@ -41,7 +41,7 @@ private enum ChangePhoneNumberCodeTag: ItemListItemTag {
} }
private enum ChangePhoneNumberCodeEntry: ItemListNodeEntry { private enum ChangePhoneNumberCodeEntry: ItemListNodeEntry {
case codeEntry(PresentationTheme, String, String) case codeEntry(PresentationTheme, PresentationStrings, String, String)
case codeInfo(PresentationTheme, String) case codeInfo(PresentationTheme, String)
var section: ItemListSectionId { var section: ItemListSectionId {
@ -59,8 +59,8 @@ private enum ChangePhoneNumberCodeEntry: ItemListNodeEntry {
static func ==(lhs: ChangePhoneNumberCodeEntry, rhs: ChangePhoneNumberCodeEntry) -> Bool { static func ==(lhs: ChangePhoneNumberCodeEntry, rhs: ChangePhoneNumberCodeEntry) -> Bool {
switch lhs { switch lhs {
case let .codeEntry(lhsTheme, lhsTitle, lhsText): case let .codeEntry(lhsTheme, lhsStrings, lhsTitle, lhsText):
if case let .codeEntry(rhsTheme, rhsTitle, rhsText) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsText == rhsText { if case let .codeEntry(rhsTheme, rhsStrings, rhsTitle, rhsText) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsTitle == rhsTitle, lhsText == rhsText {
return true return true
} else { } else {
return false return false
@ -80,8 +80,8 @@ private enum ChangePhoneNumberCodeEntry: ItemListNodeEntry {
func item(_ arguments: ChangePhoneNumberCodeControllerArguments) -> ListViewItem { func item(_ arguments: ChangePhoneNumberCodeControllerArguments) -> ListViewItem {
switch self { switch self {
case let .codeEntry(theme, title, text): case let .codeEntry(theme, strings, title, text):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(string: title, textColor: .black), text: text, placeholder: "", type: .number, spacing: 10.0, tag: ChangePhoneNumberCodeTag.input, sectionId: self.section, textUpdated: { updatedText in return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(string: title, textColor: .black), text: text, placeholder: "", type: .number, spacing: 10.0, tag: ChangePhoneNumberCodeTag.input, sectionId: self.section, textUpdated: { updatedText in
arguments.updateEntryText(updatedText) arguments.updateEntryText(updatedText)
}, action: { }, action: {
arguments.next() arguments.next()
@ -132,7 +132,7 @@ private struct ChangePhoneNumberCodeControllerState: Equatable {
private func changePhoneNumberCodeControllerEntries(presentationData: PresentationData, state: ChangePhoneNumberCodeControllerState, codeData: ChangeAccountPhoneNumberData, timeout: Int32?, strings: PresentationStrings) -> [ChangePhoneNumberCodeEntry] { private func changePhoneNumberCodeControllerEntries(presentationData: PresentationData, state: ChangePhoneNumberCodeControllerState, codeData: ChangeAccountPhoneNumberData, timeout: Int32?, strings: PresentationStrings) -> [ChangePhoneNumberCodeEntry] {
var entries: [ChangePhoneNumberCodeEntry] = [] var entries: [ChangePhoneNumberCodeEntry] = []
entries.append(.codeEntry(presentationData.theme, presentationData.strings.ChangePhoneNumberCode_CodePlaceholder, state.codeText)) entries.append(.codeEntry(presentationData.theme, presentationData.strings, presentationData.strings.ChangePhoneNumberCode_CodePlaceholder, state.codeText))
var text = authorizationCurrentOptionText(codeData.type, strings: presentationData.strings, primaryColor: presentationData.theme.list.itemPrimaryTextColor, accentColor: presentationData.theme.list.itemAccentColor).string var text = authorizationCurrentOptionText(codeData.type, strings: presentationData.strings, primaryColor: presentationData.theme.list.itemPrimaryTextColor, accentColor: presentationData.theme.list.itemAccentColor).string
if let nextType = codeData.nextType { if let nextType = codeData.nextType {
text += "\n\n" + authorizationNextOptionText(currentType: codeData.type, nextType: nextType, timeout: timeout, strings: presentationData.strings, primaryColor: .black, accentColor: .black).0.string text += "\n\n" + authorizationNextOptionText(currentType: codeData.type, nextType: nextType, timeout: timeout, strings: presentationData.strings, primaryColor: .black, accentColor: .black).0.string

View File

@ -148,7 +148,7 @@ private enum ChannelAdminEntryStableId: Hashable {
private enum ChannelAdminEntry: ItemListNodeEntry { private enum ChannelAdminEntry: ItemListNodeEntry {
case info(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, TelegramUserPresence?) case info(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, TelegramUserPresence?)
case rankTitle(PresentationTheme, String, Int32?, Int32) case rankTitle(PresentationTheme, String, Int32?, Int32)
case rank(PresentationTheme, String, String, Bool) case rank(PresentationTheme, PresentationStrings, String, String, Bool)
case rankInfo(PresentationTheme, String) case rankInfo(PresentationTheme, String)
case rightsTitle(PresentationTheme, String) case rightsTitle(PresentationTheme, String)
case rightItem(PresentationTheme, Int, String, TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags, Bool, Bool) case rightItem(PresentationTheme, Int, String, TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags, Bool, Bool)
@ -224,8 +224,8 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .rank(lhsTheme, lhsPlaceholder, lhsValue, lhsEnabled): case let .rank(lhsTheme, lhsStrings, lhsPlaceholder, lhsValue, lhsEnabled):
if case let .rank(rhsTheme, rhsPlaceholder, rhsValue, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsPlaceholder == rhsPlaceholder, lhsValue == rhsValue, lhsEnabled == rhsEnabled { if case let .rank(rhsTheme, rhsStrings, rhsPlaceholder, rhsValue, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsPlaceholder == rhsPlaceholder, lhsValue == rhsValue, lhsEnabled == rhsEnabled {
return true return true
} else { } else {
return false return false
@ -367,8 +367,8 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
accessoryText = ItemListSectionHeaderAccessoryText(value: "\(limit - count)", color: count > limit ? .destructive : .generic) accessoryText = ItemListSectionHeaderAccessoryText(value: "\(limit - count)", color: count > limit ? .destructive : .generic)
} }
return ItemListSectionHeaderItem(theme: theme, text: text, accessoryText: accessoryText, sectionId: self.section) return ItemListSectionHeaderItem(theme: theme, text: text, accessoryText: accessoryText, sectionId: self.section)
case let .rank(theme, placeholder, text, enabled): case let .rank(theme, strings, placeholder, text, enabled):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(string: "", textColor: .black), text: text, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: true), spacing: 0.0, clearButton: enabled, enabled: enabled, tag: ChannelAdminEntryTag.rank, sectionId: self.section, textUpdated: { updatedText in return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(string: "", textColor: .black), text: text, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: true), spacing: 0.0, clearButton: enabled, enabled: enabled, tag: ChannelAdminEntryTag.rank, sectionId: self.section, textUpdated: { updatedText in
arguments.updateRank(text, updatedText) arguments.updateRank(text, updatedText)
}, shouldUpdateText: { text in }, shouldUpdateText: { text in
if text.containsEmoji { if text.containsEmoji {
@ -663,7 +663,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
let rankEnabled = !state.updating && canEdit let rankEnabled = !state.updating && canEdit
entries.append(.rankTitle(presentationData.theme, presentationData.strings.Group_EditAdmin_RankTitle.uppercased(), rankEnabled && state.focusedOnRank ? Int32(currentRank?.count ?? 0) : nil, rankMaxLength)) entries.append(.rankTitle(presentationData.theme, presentationData.strings.Group_EditAdmin_RankTitle.uppercased(), rankEnabled && state.focusedOnRank ? Int32(currentRank?.count ?? 0) : nil, rankMaxLength))
entries.append(.rank(presentationData.theme, isCreator ? presentationData.strings.Group_EditAdmin_RankOwnerPlaceholder : presentationData.strings.Group_EditAdmin_RankAdminPlaceholder, currentRank ?? "", rankEnabled)) entries.append(.rank(presentationData.theme, presentationData.strings, isCreator ? presentationData.strings.Group_EditAdmin_RankOwnerPlaceholder : presentationData.strings.Group_EditAdmin_RankAdminPlaceholder, currentRank ?? "", rankEnabled))
entries.append(.rankInfo(presentationData.theme, presentationData.strings.Group_EditAdmin_RankInfo(placeholder).0)) entries.append(.rankInfo(presentationData.theme, presentationData.strings.Group_EditAdmin_RankInfo(placeholder).0))
} }
@ -689,7 +689,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
if isCreator { if isCreator {
entries.append(.rankTitle(presentationData.theme, presentationData.strings.Group_EditAdmin_RankTitle.uppercased(), rankEnabled && state.focusedOnRank ? Int32(currentRank?.count ?? 0) : nil, rankMaxLength)) entries.append(.rankTitle(presentationData.theme, presentationData.strings.Group_EditAdmin_RankTitle.uppercased(), rankEnabled && state.focusedOnRank ? Int32(currentRank?.count ?? 0) : nil, rankMaxLength))
entries.append(.rank(presentationData.theme, isCreator ? presentationData.strings.Group_EditAdmin_RankOwnerPlaceholder : presentationData.strings.Group_EditAdmin_RankAdminPlaceholder, currentRank ?? "", rankEnabled)) entries.append(.rank(presentationData.theme, presentationData.strings, isCreator ? presentationData.strings.Group_EditAdmin_RankOwnerPlaceholder : presentationData.strings.Group_EditAdmin_RankAdminPlaceholder, currentRank ?? "", rankEnabled))
} else { } else {
entries.append(.rightsTitle(presentationData.theme, presentationData.strings.Channel_EditAdmin_PermissionsHeader)) entries.append(.rightsTitle(presentationData.theme, presentationData.strings.Channel_EditAdmin_PermissionsHeader))
@ -732,7 +732,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
} }
entries.append(.rankTitle(presentationData.theme, presentationData.strings.Group_EditAdmin_RankTitle.uppercased(), rankEnabled && state.focusedOnRank ? Int32(currentRank?.count ?? 0) : nil, rankMaxLength)) entries.append(.rankTitle(presentationData.theme, presentationData.strings.Group_EditAdmin_RankTitle.uppercased(), rankEnabled && state.focusedOnRank ? Int32(currentRank?.count ?? 0) : nil, rankMaxLength))
entries.append(.rank(presentationData.theme, isCreator ? presentationData.strings.Group_EditAdmin_RankOwnerPlaceholder : presentationData.strings.Group_EditAdmin_RankAdminPlaceholder, currentRank ?? "", rankEnabled)) entries.append(.rank(presentationData.theme, presentationData.strings, isCreator ? presentationData.strings.Group_EditAdmin_RankOwnerPlaceholder : presentationData.strings.Group_EditAdmin_RankAdminPlaceholder, currentRank ?? "", rankEnabled))
if let initialParticipant = initialParticipant, case let .member(participant) = initialParticipant, let adminInfo = participant.adminInfo, !adminInfo.rights.flags.isEmpty && admin.id != accountPeerId { if let initialParticipant = initialParticipant, case let .member(participant) = initialParticipant, let adminInfo = participant.adminInfo, !adminInfo.rights.flags.isEmpty && admin.id != accountPeerId {
entries.append(.dismiss(presentationData.theme, presentationData.strings.Channel_Moderator_AccessLevelRevoke)) entries.append(.dismiss(presentationData.theme, presentationData.strings.Channel_Moderator_AccessLevelRevoke))

View File

@ -64,7 +64,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
case publicLinkHeader(PresentationTheme, String) case publicLinkHeader(PresentationTheme, String)
case publicLinkAvailability(PresentationTheme, String, Bool) case publicLinkAvailability(PresentationTheme, String, Bool)
case privateLink(PresentationTheme, String, String?) case privateLink(PresentationTheme, String, String?)
case editablePublicLink(PresentationTheme, String, String) case editablePublicLink(PresentationTheme, PresentationStrings, String, String)
case privateLinkInfo(PresentationTheme, String) case privateLinkInfo(PresentationTheme, String)
case privateLinkCopy(PresentationTheme, String) case privateLinkCopy(PresentationTheme, String)
case privateLinkRevoke(PresentationTheme, String) case privateLinkRevoke(PresentationTheme, String)
@ -169,8 +169,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .editablePublicLink(lhsTheme, lhsPlaceholder, lhsCurrentText): case let .editablePublicLink(lhsTheme, lhsStrings, lhsPlaceholder, lhsCurrentText):
if case let .editablePublicLink(rhsTheme, rhsPlaceholder, rhsCurrentText) = rhs, lhsTheme === rhsTheme, lhsPlaceholder == rhsPlaceholder, lhsCurrentText == rhsCurrentText { if case let .editablePublicLink(rhsTheme, rhsStrings, rhsPlaceholder, rhsCurrentText) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsPlaceholder == rhsPlaceholder, lhsCurrentText == rhsCurrentText {
return true return true
} else { } else {
return false return false
@ -280,8 +280,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
arguments.displayPrivateLinkMenu(value) arguments.displayPrivateLinkMenu(value)
} }
}, tag: ChannelVisibilityEntryTag.privateLink) }, tag: ChannelVisibilityEntryTag.privateLink)
case let .editablePublicLink(theme, placeholder, currentText): case let .editablePublicLink(theme, strings, placeholder, currentText):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(string: "t.me/", textColor: theme.list.itemPrimaryTextColor), text: currentText, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: false), clearButton: true, tag: ChannelVisibilityEntryTag.publicLink, sectionId: self.section, textUpdated: { updatedText in return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(string: "t.me/", textColor: theme.list.itemPrimaryTextColor), text: currentText, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: false), clearButton: true, tag: ChannelVisibilityEntryTag.publicLink, sectionId: self.section, textUpdated: { updatedText in
arguments.updatePublicLinkText(currentText, updatedText) arguments.updatePublicLinkText(currentText, updatedText)
}, updatedFocus: { focus in }, updatedFocus: { focus in
if focus { if focus {
@ -527,7 +527,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp, true)) entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp, true))
} }
} else { } else {
entries.append(.editablePublicLink(presentationData.theme, presentationData.strings.Group_PublicLink_Placeholder, currentAddressName)) entries.append(.editablePublicLink(presentationData.theme, presentationData.strings, presentationData.strings.Group_PublicLink_Placeholder, currentAddressName))
if let status = state.addressNameValidationStatus { if let status = state.addressNameValidationStatus {
let text: String let text: String
switch status { switch status {
@ -669,7 +669,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp, true)) entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp, true))
} }
} else { } else {
entries.append(.editablePublicLink(presentationData.theme, "", currentAddressName)) entries.append(.editablePublicLink(presentationData.theme, presentationData.strings, "", currentAddressName))
if let status = state.addressNameValidationStatus { if let status = state.addressNameValidationStatus {
let text: String let text: String
switch status { switch status {

View File

@ -1468,7 +1468,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
} }
chatInfoButtonItem.target = self chatInfoButtonItem.target = self
chatInfoButtonItem.action = #selector(self.rightNavigationButtonAction) chatInfoButtonItem.action = #selector(self.rightNavigationButtonAction)
chatInfoButtonItem.accessibilityLabel = "Info" chatInfoButtonItem.accessibilityLabel = self.presentationData.strings.Conversation_Info
self.chatInfoNavigationButton = ChatNavigationButton(action: .openChatInfo, buttonItem: chatInfoButtonItem) self.chatInfoNavigationButton = ChatNavigationButton(action: .openChatInfo, buttonItem: chatInfoButtonItem)
self.updateChatPresentationInterfaceState(animated: false, interactive: false, { state in self.updateChatPresentationInterfaceState(animated: false, interactive: false, { state in

View File

@ -1319,7 +1319,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
} }
if let textInputPanelNode = self.textInputPanelNode, updateInputTextState { if let textInputPanelNode = self.textInputPanelNode, updateInputTextState {
textInputPanelNode.updateInputTextState(chatPresentationInterfaceState.interfaceState.effectiveInputState, keepSendButtonEnabled: keepSendButtonEnabled, extendedSearchLayout: extendedSearchLayout, animated: transition.isAnimated)
textInputPanelNode.updateInputTextState(chatPresentationInterfaceState.interfaceState.effectiveInputState, keepSendButtonEnabled: keepSendButtonEnabled, extendedSearchLayout: extendedSearchLayout, accessoryItems: chatPresentationInterfaceState.inputTextPanelState.accessoryItems, animated: transition.isAnimated)
} else { } else {
self.textInputPanelNode?.updateKeepSendButtonEnabled(keepSendButtonEnabled: keepSendButtonEnabled, extendedSearchLayout: extendedSearchLayout, animated: transition.isAnimated) self.textInputPanelNode?.updateKeepSendButtonEnabled(keepSendButtonEnabled: keepSendButtonEnabled, extendedSearchLayout: extendedSearchLayout, animated: transition.isAnimated)
} }

View File

@ -36,7 +36,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
currentPanel.updateTheme(theme: chatPresentationInterfaceState.theme) currentPanel.updateTheme(theme: chatPresentationInterfaceState.theme)
return currentPanel return currentPanel
} else { } else {
let panel = ChatMessageSelectionInputPanelNode(theme: chatPresentationInterfaceState.theme) let panel = ChatMessageSelectionInputPanelNode(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
panel.context = context panel.context = context
panel.selectedMessages = selectionState.selectedIds panel.selectedMessages = selectionState.selectedIds
panel.interfaceInteraction = interfaceInteraction panel.interfaceInteraction = interfaceInteraction

View File

@ -54,7 +54,7 @@ func rightNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Ch
} else if let peer = presentationInterfaceState.renderedPeer?.peer { } else if let peer = presentationInterfaceState.renderedPeer?.peer {
if presentationInterfaceState.accountPeerId == peer.id { if presentationInterfaceState.accountPeerId == peer.id {
let buttonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(presentationInterfaceState.theme), style: .plain, target: target, action: selector) let buttonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(presentationInterfaceState.theme), style: .plain, target: target, action: selector)
buttonItem.accessibilityLabel = "Info" buttonItem.accessibilityLabel = strings.Conversation_Info
return ChatNavigationButton(action: .search, buttonItem: buttonItem) return ChatNavigationButton(action: .search, buttonItem: buttonItem)
} }
} }

View File

@ -112,7 +112,7 @@ public class ChatListController: TelegramController, UIViewControllerPreviewingD
self.presentationData = (context.sharedContext.currentPresentationData.with { $0 }) self.presentationData = (context.sharedContext.currentPresentationData.with { $0 })
self.presentationDataValue.set(.single(self.presentationData)) self.presentationDataValue.set(.single(self.presentationData))
self.titleView = ChatListTitleView(theme: self.presentationData.theme) self.titleView = ChatListTitleView(theme: self.presentationData.theme, strings: self.presentationData.strings)
super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .always, locationBroadcastPanelSource: .summary) super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .always, locationBroadcastPanelSource: .summary)
@ -147,7 +147,7 @@ public class ChatListController: TelegramController, UIViewControllerPreviewingD
self.navigationItem.leftBarButtonItem = leftBarButtonItem self.navigationItem.leftBarButtonItem = leftBarButtonItem
let rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationComposeIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.composePressed)) let rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationComposeIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.composePressed))
rightBarButtonItem.accessibilityLabel = "Compose" rightBarButtonItem.accessibilityLabel = self.presentationData.strings.VoiceOver_Navigation_Compose
self.navigationItem.rightBarButtonItem = rightBarButtonItem self.navigationItem.rightBarButtonItem = rightBarButtonItem
let backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.DialogList_Title, style: .plain, target: nil, action: nil) let backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.DialogList_Title, style: .plain, target: nil, action: nil)
backBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Back backBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Back
@ -236,7 +236,7 @@ public class ChatListController: TelegramController, UIViewControllerPreviewingD
if case .root = strongSelf.groupId { if case .root = strongSelf.groupId {
isRoot = true isRoot = true
let rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationComposeIcon(strongSelf.presentationData.theme), style: .plain, target: strongSelf, action: #selector(strongSelf.composePressed)) let rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationComposeIcon(strongSelf.presentationData.theme), style: .plain, target: strongSelf, action: #selector(strongSelf.composePressed))
rightBarButtonItem.accessibilityLabel = "Compose" rightBarButtonItem.accessibilityLabel = strongSelf.presentationData.strings.VoiceOver_Navigation_Compose
strongSelf.navigationItem.rightBarButtonItem = rightBarButtonItem strongSelf.navigationItem.rightBarButtonItem = rightBarButtonItem
} }
@ -393,7 +393,7 @@ public class ChatListController: TelegramController, UIViewControllerPreviewingD
if case .root = self.groupId { if case .root = self.groupId {
self.navigationItem.leftBarButtonItem = editItem self.navigationItem.leftBarButtonItem = editItem
let rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationComposeIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.composePressed)) let rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationComposeIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.composePressed))
rightBarButtonItem.accessibilityLabel = "Compose" rightBarButtonItem.accessibilityLabel = self.presentationData.strings.VoiceOver_Navigation_Compose
self.navigationItem.rightBarButtonItem = rightBarButtonItem self.navigationItem.rightBarButtonItem = rightBarButtonItem
} else { } else {
self.navigationItem.rightBarButtonItem = editItem self.navigationItem.rightBarButtonItem = editItem

View File

@ -262,6 +262,16 @@ private func leftRevealOptions(strings: PresentationStrings, theme: Presentation
return options return options
} }
private final class ChatListItemAccessibilityCustomAction: UIAccessibilityCustomAction {
let key: Int32
init(name: String, target: Any?, selector: Selector, key: Int32) {
self.key = key
super.init(name: name, target: target, selector: selector)
}
}
private let separatorHeight = 1.0 / UIScreen.main.scale private let separatorHeight = 1.0 / UIScreen.main.scale
private let avatarFont = UIFont(name: ".SFCompactRounded-Semibold", size: 26.0)! private let avatarFont = UIFont(name: ".SFCompactRounded-Semibold", size: 26.0)!
@ -1078,6 +1088,14 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let contentImageSize = CGSize(width: 22.0, height: 22.0) let contentImageSize = CGSize(width: 22.0, height: 22.0)
var customActions: [ChatListItemAccessibilityCustomAction] = []
for option in peerLeftRevealOptions {
customActions.append(ChatListItemAccessibilityCustomAction(name: option.title, target: nil, selector: #selector(ChatListItemNode.performLocalAccessibilityCustomAction(_:)), key: option.key))
}
for option in peerRevealOptions {
customActions.append(ChatListItemAccessibilityCustomAction(name: option.title, target: nil, selector: #selector(ChatListItemNode.performLocalAccessibilityCustomAction(_:)), key: option.key))
}
return (layout, { [weak self] synchronousLoads, animated in return (layout, { [weak self] synchronousLoads, animated in
if let strongSelf = self { if let strongSelf = self {
strongSelf.layoutParams = (item, first, last, firstWithHeader, nextIsPinned, params, countersSize) strongSelf.layoutParams = (item, first, last, firstWithHeader, nextIsPinned, params, countersSize)
@ -1449,6 +1467,14 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
strongSelf.view.accessibilityLabel = strongSelf.accessibilityLabel strongSelf.view.accessibilityLabel = strongSelf.accessibilityLabel
strongSelf.view.accessibilityValue = strongSelf.accessibilityValue strongSelf.view.accessibilityValue = strongSelf.accessibilityValue
if !customActions.isEmpty {
strongSelf.view.accessibilityCustomActions = customActions.map({ action -> UIAccessibilityCustomAction in
return ChatListItemAccessibilityCustomAction(name: action.name, target: strongSelf, selector: #selector(strongSelf.performLocalAccessibilityCustomAction(_:)), key: action.key)
})
} else {
strongSelf.view.accessibilityCustomActions = nil
}
} }
}) })
} }
@ -1705,4 +1731,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} }
} }
} }
@objc private func performLocalAccessibilityCustomAction(_ action: UIAccessibilityCustomAction) {
if let action = action as? ChatListItemAccessibilityCustomAction {
self.revealOptionSelected(ItemListRevealOption(key: action.key, title: "", icon: .none, color: .black, textColor: .white), animated: false)
}
}
} }

View File

@ -68,8 +68,11 @@ final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitl
} }
} }
init(theme: PresentationTheme) { var strings: PresentationStrings
init(theme: PresentationTheme, strings: PresentationStrings) {
self.theme = theme self.theme = theme
self.strings = strings
self.titleNode = ImmediateTextNode() self.titleNode = ImmediateTextNode()
self.titleNode.displaysAsynchronously = false self.titleNode.displaysAsynchronously = false
@ -96,7 +99,7 @@ final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitl
self.proxyButton = HighlightTrackingButton() self.proxyButton = HighlightTrackingButton()
self.proxyButton.isHidden = true self.proxyButton.isHidden = true
self.proxyButton.isAccessibilityElement = true self.proxyButton.isAccessibilityElement = true
self.proxyButton.accessibilityLabel = "Proxy Settings" self.proxyButton.accessibilityLabel = self.strings.VoiceOver_Navigation_ProxySettings
self.proxyButton.accessibilityTraits = .button self.proxyButton.accessibilityTraits = .button
super.init(frame: CGRect()) super.init(frame: CGRect())
@ -213,7 +216,7 @@ final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitl
} }
func makeTransitionMirrorNode() -> ASDisplayNode { func makeTransitionMirrorNode() -> ASDisplayNode {
let view = ChatListTitleView(theme: self.theme) let view = ChatListTitleView(theme: self.theme, strings: self.strings)
view.title = self.title view.title = self.title
return ASDisplayNode(viewBlock: { return ASDisplayNode(viewBlock: {

View File

@ -20,6 +20,8 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
var longTapRecognizer: UILongPressGestureRecognizer? var longTapRecognizer: UILongPressGestureRecognizer?
private let accessibilityArea: AccessibilityAreaNode
override init() { override init() {
self.backgroundNode = ASImageNode() self.backgroundNode = ASImageNode()
self.backgroundNode.displayWithoutProcessing = true self.backgroundNode.displayWithoutProcessing = true
@ -28,9 +30,18 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
self.backgroundNode.alpha = 1.0 self.backgroundNode.alpha = 1.0
self.backgroundNode.isUserInteractionEnabled = false self.backgroundNode.isUserInteractionEnabled = false
self.accessibilityArea = AccessibilityAreaNode()
self.accessibilityArea.accessibilityTraits = .button
super.init() super.init()
self.addSubnode(self.backgroundNode) self.addSubnode(self.backgroundNode)
self.addSubnode(self.accessibilityArea)
self.accessibilityArea.activate = { [weak self] in
self?.buttonPressed()
return true
}
} }
override func didLoad() { override func didLoad() {
@ -39,6 +50,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
let buttonView = HighlightTrackingButton(frame: self.bounds) let buttonView = HighlightTrackingButton(frame: self.bounds)
buttonView.addTarget(self, action: #selector(self.buttonPressed), for: [.touchUpInside]) buttonView.addTarget(self, action: #selector(self.buttonPressed), for: [.touchUpInside])
self.buttonView = buttonView self.buttonView = buttonView
buttonView.isAccessibilityElement = false
self.view.addSubview(buttonView) self.view.addSubview(buttonView)
buttonView.highligthedChanged = { [weak self] highlighted in buttonView.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self { if let strongSelf = self {
@ -168,6 +180,9 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
node.buttonView?.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: 42.0)) node.buttonView?.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: 42.0))
node.iconNode?.frame = CGRect(x: width - 16.0, y: 4.0, width: 12.0, height: 12.0) node.iconNode?.frame = CGRect(x: width - 16.0, y: 4.0, width: 12.0, height: 12.0)
node.accessibilityArea.accessibilityLabel = title
node.accessibilityArea.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: 42.0))
return node return node
}) })
}) })

View File

@ -189,6 +189,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
} }
return false return false
} }
self.messageAccessibilityArea.focused = { [weak self] in
self?.accessibilityElementDidBecomeFocused()
}
} }
required init?(coder aDecoder: NSCoder) { required init?(coder aDecoder: NSCoder) {

View File

@ -174,16 +174,18 @@ final class ChatMessageAccessibilityData {
if let _ = media as? TelegramMediaImage { if let _ = media as? TelegramMediaImage {
if isIncoming { if isIncoming {
if announceIncomingAuthors, let authorName = authorName { if announceIncomingAuthors, let authorName = authorName {
label = "Photo, from: \(authorName)" label = item.presentationData.strings.VoiceOver_Chat_PhotoFrom(authorName).0
} else { } else {
label = "Photo" label = item.presentationData.strings.VoiceOver_Chat_Photo
} }
} else { } else {
label = "Your photo" label = item.presentationData.strings.VoiceOver_Chat_YourPhoto
} }
text = "" text = ""
if !item.message.text.isEmpty { if !item.message.text.isEmpty {
text.append("\nCaption: \(item.message.text)") text.append("\n")
text.append(item.presentationData.strings.VoiceOver_Chat_Caption(item.message.text).0)
} }
} else if let file = media as? TelegramMediaFile { } else if let file = media as? TelegramMediaFile {
var isSpecialFile = false var isSpecialFile = false
@ -192,93 +194,98 @@ final class ChatMessageAccessibilityData {
case let .Audio(audio): case let .Audio(audio):
isSpecialFile = true isSpecialFile = true
if isSelected == nil { if isSelected == nil {
hint = "Double tap to play" hint = item.presentationData.strings.VoiceOver_Chat_PlayHint
} }
traits.insert(.startsMediaSession) traits.insert(.startsMediaSession)
if audio.isVoice { if audio.isVoice {
let durationString = voiceMessageDurationFormatter.string(from: Double(audio.duration)) ?? "" let durationString = voiceMessageDurationFormatter.string(from: Double(audio.duration)) ?? ""
if isIncoming { if isIncoming {
if announceIncomingAuthors, let authorName = authorName { if announceIncomingAuthors, let authorName = authorName {
label = "Voice message, from: \(authorName)" label = item.presentationData.strings.VoiceOver_Chat_VoiceMessageFrom(authorName).0
} else { } else {
label = "Voice message" label = item.presentationData.strings.VoiceOver_Chat_VoiceMessage
} }
} else { } else {
label = "Your voice message" label = item.presentationData.strings.VoiceOver_Chat_YourVoiceMessage
} }
text = "Duration: \(durationString)" text = item.presentationData.strings.VoiceOver_Chat_Duration(durationString).0
} else { } else {
let durationString = musicDurationFormatter.string(from: Double(audio.duration)) ?? "" let durationString = musicDurationFormatter.string(from: Double(audio.duration)) ?? ""
if isIncoming { if isIncoming {
if announceIncomingAuthors, let authorName = authorName { if announceIncomingAuthors, let authorName = authorName {
label = "Music file, from: \(authorName)" label = item.presentationData.strings.VoiceOver_Chat_MusicFrom(authorName).0
} else { } else {
label = "Music file" label = item.presentationData.strings.VoiceOver_Chat_Music
} }
} else { } else {
label = "Your music file" label = item.presentationData.strings.VoiceOver_Chat_YourMusic
} }
let performer = audio.performer ?? "Unknown" let performer = audio.performer ?? "Unknown"
let title = audio.title ?? "Unknown" let title = audio.title ?? "Unknown"
text = "\(title), by \(performer). Duration: \(durationString)"
text = item.presentationData.strings.VoiceOver_Chat_MusicTitle(title, performer).0
text.append(item.presentationData.strings.VoiceOver_Chat_Duration(durationString).0)
} }
case let .Video(video): case let .Video(video):
isSpecialFile = true isSpecialFile = true
if isSelected == nil { if isSelected == nil {
hint = "Double tap to play" hint = item.presentationData.strings.VoiceOver_Chat_PlayHint
} }
traits.insert(.startsMediaSession) traits.insert(.startsMediaSession)
let durationString = voiceMessageDurationFormatter.string(from: Double(video.duration)) ?? "" let durationString = voiceMessageDurationFormatter.string(from: Double(video.duration)) ?? ""
if video.flags.contains(.instantRoundVideo) { if video.flags.contains(.instantRoundVideo) {
if isIncoming { if isIncoming {
if announceIncomingAuthors, let authorName = authorName { if announceIncomingAuthors, let authorName = authorName {
label = "Video message, from: \(authorName)" label = item.presentationData.strings.VoiceOver_Chat_VideoMessageFrom(authorName).0
} else { } else {
label = "Video message" label = item.presentationData.strings.VoiceOver_Chat_VideoMessage
} }
} else { } else {
label = "Your video message" label = item.presentationData.strings.VoiceOver_Chat_YourVideoMessage
} }
} else { } else {
if isIncoming { if isIncoming {
if announceIncomingAuthors, let authorName = authorName { if announceIncomingAuthors, let authorName = authorName {
label = "Video, from: \(authorName)" label = item.presentationData.strings.VoiceOver_Chat_VideoFrom(authorName).0
} else { } else {
label = "Video" label = item.presentationData.strings.VoiceOver_Chat_Video
} }
} else { } else {
label = "Your video" label = item.presentationData.strings.VoiceOver_Chat_YourVideo
} }
} }
text = "Duration: \(durationString)" text = item.presentationData.strings.VoiceOver_Chat_Duration(durationString).0
default: default:
break break
} }
} }
if !isSpecialFile { if !isSpecialFile {
if isSelected == nil { if isSelected == nil {
hint = "Double tap to open" hint = item.presentationData.strings.VoiceOver_Chat_OpenHint
} }
let sizeString = fileSizeFormatter.string(fromByteCount: Int64(file.size ?? 0)) let sizeString = fileSizeFormatter.string(fromByteCount: Int64(file.size ?? 0))
if isIncoming { if isIncoming {
if announceIncomingAuthors, let authorName = authorName { if announceIncomingAuthors, let authorName = authorName {
label = "File, from: \(authorName)" label = item.presentationData.strings.VoiceOver_Chat_FileFrom(authorName).0
} else { } else {
label = "File" label = item.presentationData.strings.VoiceOver_Chat_File
} }
} else { } else {
label = "Your file" label = item.presentationData.strings.VoiceOver_Chat_YourFile
} }
text = "\(file.fileName ?? ""). Size: \(sizeString)" text = "\(file.fileName ?? ""). "
text.append(item.presentationData.strings.VoiceOver_Chat_Size(sizeString).0)
} }
if !item.message.text.isEmpty { if !item.message.text.isEmpty {
text.append("\nCaption: \(item.message.text)") text.append("\n")
text.append(item.presentationData.strings.VoiceOver_Chat_Caption(item.message.text).0)
} }
break loop break loop
} else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { } else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
var contentText = "Page preview. " var contentText = item.presentationData.strings.VoiceOver_Chat_PagePreview + ". "
if let title = content.title, !title.isEmpty { if let title = content.title, !title.isEmpty {
contentText.append("Title: \(title). ") contentText.append(item.presentationData.strings.VoiceOver_Chat_Title(title).0)
contentText.append(". ")
} }
if let text = content.text, !text.isEmpty { if let text = content.text, !text.isEmpty {
contentText.append(text) contentText.append(text)
@ -287,12 +294,12 @@ final class ChatMessageAccessibilityData {
} else if let contact = media as? TelegramMediaContact { } else if let contact = media as? TelegramMediaContact {
if isIncoming { if isIncoming {
if announceIncomingAuthors, let authorName = authorName { if announceIncomingAuthors, let authorName = authorName {
label = "Shared contact, from: \(authorName)" label = item.presentationData.strings.VoiceOver_Chat_ContactFrom(authorName).0
} else { } else {
label = "Shared contact" label = item.presentationData.strings.VoiceOver_Chat_Contact
} }
} else { } else {
label = "Your shared contact" label = item.presentationData.strings.VoiceOver_Chat_YourContact
} }
var displayName = "" var displayName = ""
if !contact.firstName.isEmpty { if !contact.firstName.isEmpty {
@ -348,32 +355,36 @@ final class ChatMessageAccessibilityData {
text = "\(displayName)." text = "\(displayName)."
if !phoneNumbersString.isEmpty { if !phoneNumbersString.isEmpty {
if phoneNumberCount > 1 { if phoneNumberCount > 1 {
text.append("\(phoneNumberCount) phone numbers: ") text.append(item.presentationData.strings.VoiceOver_Chat_ContactPhoneNumberCount(Int32(phoneNumberCount)))
text.append(": ")
} else { } else {
text.append("Phone number: ") text.append(item.presentationData.strings.VoiceOver_Chat_ContactPhoneNumber)
} }
text.append("\(phoneNumbersString). ") text.append("\(phoneNumbersString). ")
} }
if !emailAddressesString.isEmpty { if !emailAddressesString.isEmpty {
if emailAddressCount > 1 { if emailAddressCount > 1 {
text.append("\(emailAddressCount) email addresses: ") text.append(item.presentationData.strings.VoiceOver_Chat_ContactEmailCount(Int32(emailAddressCount)))
text.append(": ")
} else { } else {
text.append("Email: ") text.append(item.presentationData.strings.VoiceOver_Chat_ContactEmail)
text.append(": ")
} }
text.append("\(emailAddressesString). ") text.append("\(emailAddressesString). ")
} }
if !organizationString.isEmpty { if !organizationString.isEmpty {
text.append("Organization: \(organizationString).") text.append(item.presentationData.strings.VoiceOver_Chat_ContactOrganization(organizationString).0)
text.append(".")
} }
} else if let poll = media as? TelegramMediaPoll { } else if let poll = media as? TelegramMediaPoll {
if isIncoming { if isIncoming {
if announceIncomingAuthors, let authorName = authorName { if announceIncomingAuthors, let authorName = authorName {
label = "Anonymous poll, from: \(authorName)" label = item.presentationData.strings.VoiceOver_Chat_AnonymousPollFrom(authorName).0
} else { } else {
label = "Anonymous poll" label = item.presentationData.strings.VoiceOver_Chat_AnonymousPoll
} }
} else { } else {
label = "Your anonymous poll" label = item.presentationData.strings.VoiceOver_Chat_YourAnonymousPoll
} }
var optionVoterCount: [Int: Int32] = [:] var optionVoterCount: [Int: Int32] = [:]
@ -415,9 +426,11 @@ final class ChatMessageAccessibilityData {
optionVoterCounts = Array(repeating: 0, count: poll.options.count) optionVoterCounts = Array(repeating: 0, count: poll.options.count)
} }
text = "Title: \(poll.text). " text = item.presentationData.strings.VoiceOver_Chat_Title(poll.text).0
text.append(". ")
text.append("\(poll.options.count) options: ") text.append(item.presentationData.strings.VoiceOver_Chat_PollOptionCount(Int32(poll.options.count)))
text.append(": ")
var optionsText = "" var optionsText = ""
for i in 0 ..< poll.options.count { for i in 0 ..< poll.options.count {
let option = poll.options[i] let option = poll.options[i]
@ -427,7 +440,8 @@ final class ChatMessageAccessibilityData {
} }
optionsText.append(option.text) optionsText.append(option.text)
if let selectedOptionId = selectedOptionId, selectedOptionId == option.opaqueIdentifier { if let selectedOptionId = selectedOptionId, selectedOptionId == option.opaqueIdentifier {
optionsText.append(", selected") optionsText.append(", ")
optionsText.append(item.presentationData.strings.VoiceOver_Chat_OptionSelected)
} }
if let _ = optionVoterCount[i] { if let _ = optionVoterCount[i] {
@ -438,16 +452,12 @@ final class ChatMessageAccessibilityData {
} }
text.append("\(optionsText). ") text.append("\(optionsText). ")
if totalVoterCount != 0 { if totalVoterCount != 0 {
if totalVoterCount == 1 { text.append(item.presentationData.strings.VoiceOver_Chat_PollVotes(Int32(totalVoterCount)))
text.append("1 vote. ")
} else { } else {
text.append("\(totalVoterCount) votes. ") text.append(item.presentationData.strings.VoiceOver_Chat_PollNoVotes)
}
} else {
text.append("No votes. ")
} }
if poll.isClosed { if poll.isClosed {
text.append("Final results. ") text.append(item.presentationData.strings.VoiceOver_Chat_PollFinalResults)
} }
} }
} }
@ -456,7 +466,8 @@ final class ChatMessageAccessibilityData {
if let isSelected = isSelected { if let isSelected = isSelected {
if isSelected { if isSelected {
result += "Selected.\n" result += item.presentationData.strings.VoiceOver_Chat_Selected
result += "\n"
} }
traits.insert(.startsMediaSession) traits.insert(.startsMediaSession)
} }
@ -468,9 +479,9 @@ final class ChatMessageAccessibilityData {
result += "\n\(dateString)" result += "\n\(dateString)"
if !isIncoming && item.read { if !isIncoming && item.read {
if announceIncomingAuthors { if announceIncomingAuthors {
result += "Seen by recipients" result += item.presentationData.strings.VoiceOver_Chat_SeenByRecipients
} else { } else {
result += "Seen by recipient" result += item.presentationData.strings.VoiceOver_Chat_SeenByRecipient
} }
} }
value = result value = result
@ -483,10 +494,10 @@ final class ChatMessageAccessibilityData {
if isIncoming { if isIncoming {
label = author.displayTitle label = author.displayTitle
} else { } else {
label = "Your message" label = item.presentationData.strings.VoiceOver_Chat_YourMessage
} }
} else { } else {
label = "Message" label = item.presentationData.strings.VoiceOver_Chat_Message
} }
} }
@ -521,25 +532,25 @@ final class ChatMessageAccessibilityData {
let replyLabel: String let replyLabel: String
if replyMessage.flags.contains(.Incoming) { if replyMessage.flags.contains(.Incoming) {
if let author = replyMessage.author { if let author = replyMessage.author {
replyLabel = "Reply to message from \(author.displayTitle)" replyLabel = item.presentationData.strings.VoiceOver_Chat_ReplyFrom(author.displayTitle).0
} else { } else {
replyLabel = "Reply to message" replyLabel = item.presentationData.strings.VoiceOver_Chat_Reply
} }
} else { } else {
replyLabel = "Reply to your message" replyLabel = item.presentationData.strings.VoiceOver_Chat_ReplyToYourMessage
} }
label = "\(replyLabel) . \(label)" label = "\(replyLabel) . \(label)"
} }
} }
if hint == nil && singleUrl != nil { if hint == nil && singleUrl != nil {
hint = "Double tap to open link" hint = item.presentationData.strings.VoiceOver_Chat_OpenLinkHint
} }
if let forwardInfo = item.message.forwardInfo { if let forwardInfo = item.message.forwardInfo {
let forwardLabel: String let forwardLabel: String
if let author = forwardInfo.author, author.id == item.context.account.peerId { if let author = forwardInfo.author, author.id == item.context.account.peerId {
forwardLabel = "Forwarded from you" forwardLabel = item.presentationData.strings.VoiceOver_Chat_ForwardedFromYou
} else { } else {
let peerString: String let peerString: String
if let peer = forwardInfo.author { if let peer = forwardInfo.author {
@ -553,7 +564,7 @@ final class ChatMessageAccessibilityData {
} else { } else {
peerString = "" peerString = ""
} }
forwardLabel = "Forwarded from \(peerString)" forwardLabel = item.presentationData.strings.VoiceOver_Chat_ForwardedFrom(peerString).0
} }
label = "\(forwardLabel). \(label)" label = "\(forwardLabel). \(label)"
} }
@ -573,9 +584,9 @@ final class ChatMessageAccessibilityData {
} }
if canReply { if canReply {
customActions.append(ChatMessageAccessibilityCustomAction(name: "Reply", target: nil, selector: #selector(self.noop), action: .reply)) customActions.append(ChatMessageAccessibilityCustomAction(name: item.presentationData.strings.VoiceOver_MessageContextReply, target: nil, selector: #selector(self.noop), action: .reply))
} }
customActions.append(ChatMessageAccessibilityCustomAction(name: "Open message menu", target: nil, selector: #selector(self.noop), action: .options)) customActions.append(ChatMessageAccessibilityCustomAction(name: item.presentationData.strings.VoiceOver_MessageContextOpenMessageMenu, target: nil, selector: #selector(self.noop), action: .options))
} }
self.label = label self.label = label

View File

@ -466,7 +466,7 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
} }
} }
node.buttonNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: contentHeight)) node.buttonNode.frame = CGRect(origin: CGPoint(x: 1.0, y: 0.0), size: CGSize(width: width - 2.0, height: contentHeight))
node.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: width, height: contentHeight + UIScreenPixel)) node.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: width, height: contentHeight + UIScreenPixel))
node.separatorNode.backgroundColor = incoming ? presentationData.theme.theme.chat.message.incoming.polls.separator : presentationData.theme.theme.chat.message.outgoing.polls.separator node.separatorNode.backgroundColor = incoming ? presentationData.theme.theme.chat.message.incoming.polls.separator : presentationData.theme.theme.chat.message.outgoing.polls.separator
@ -495,6 +495,8 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
} }
} }
node.buttonNode.isAccessibilityElement = shouldHaveRadioNode
let previousResultBarWidth = minBarWidth + floor((width - leftInset - rightInset - minBarWidth) * (currentResult?.normalized ?? 0.0)) let previousResultBarWidth = minBarWidth + floor((width - leftInset - rightInset - minBarWidth) * (currentResult?.normalized ?? 0.0))
let previousFrame = CGRect(origin: CGPoint(x: leftInset, y: contentHeight - 6.0 - 1.0), size: CGSize(width: previousResultBarWidth, height: 6.0)) let previousFrame = CGRect(origin: CGPoint(x: leftInset, y: contentHeight - 6.0 - 1.0), size: CGSize(width: previousResultBarWidth, height: 6.0))

View File

@ -47,27 +47,27 @@ final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode {
} }
} }
init(theme: PresentationTheme) { init(theme: PresentationTheme, strings: PresentationStrings) {
self.theme = theme self.theme = theme
self.deleteButton = HighlightableButtonNode() self.deleteButton = HighlightableButtonNode()
self.deleteButton.isEnabled = false self.deleteButton.isEnabled = false
self.deleteButton.isAccessibilityElement = true self.deleteButton.isAccessibilityElement = true
self.deleteButton.accessibilityLabel = "Delete" self.deleteButton.accessibilityLabel = strings.VoiceOver_MessageContextDelete
self.reportButton = HighlightableButtonNode() self.reportButton = HighlightableButtonNode()
self.reportButton.isEnabled = false self.reportButton.isEnabled = false
self.reportButton.isAccessibilityElement = true self.reportButton.isAccessibilityElement = true
self.reportButton.accessibilityLabel = "Report" self.reportButton.accessibilityLabel = strings.VoiceOver_MessageContextReport
self.forwardButton = HighlightableButtonNode() self.forwardButton = HighlightableButtonNode()
self.forwardButton.isAccessibilityElement = true self.forwardButton.isAccessibilityElement = true
self.forwardButton.accessibilityLabel = "Forward" self.forwardButton.accessibilityLabel = strings.VoiceOver_MessageContextForward
self.shareButton = HighlightableButtonNode() self.shareButton = HighlightableButtonNode()
self.shareButton.isEnabled = false self.shareButton.isEnabled = false
self.shareButton.isAccessibilityElement = true self.shareButton.isAccessibilityElement = true
self.shareButton.accessibilityLabel = "Share" self.shareButton.accessibilityLabel = strings.VoiceOver_MessageContextShare
self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionThrash"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionThrash"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal])
self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionThrash"), color: theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled]) self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionThrash"), color: theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled])

View File

@ -108,9 +108,9 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
updateWaveform = true updateWaveform = true
} }
if self.presentationInterfaceState?.strings !== interfaceState.strings { if self.presentationInterfaceState?.strings !== interfaceState.strings {
self.deleteButton.accessibilityLabel = "Delete" self.deleteButton.accessibilityLabel = interfaceState.strings.VoiceOver_MessageContextDelete
self.sendButton.accessibilityLabel = "Send" self.sendButton.accessibilityLabel = interfaceState.strings.VoiceOver_MessageContextSend
self.waveformButton.accessibilityLabel = "Preview voice message" self.waveformButton.accessibilityLabel = interfaceState.strings.VoiceOver_Chat_RecordPreviewVoiceMessage
} }
self.presentationInterfaceState = interfaceState self.presentationInterfaceState = interfaceState

View File

@ -5,6 +5,8 @@ import Display
import TelegramPresentationData import TelegramPresentationData
final class ChatTextInputActionButtonsNode: ASDisplayNode { final class ChatTextInputActionButtonsNode: ASDisplayNode {
private let strings: PresentationStrings
let micButton: ChatTextInputMediaRecordingButton let micButton: ChatTextInputMediaRecordingButton
let sendButton: HighlightTrackingButton let sendButton: HighlightTrackingButton
var sendButtonRadialStatusNode: ChatSendButtonRadialStatusNode? var sendButtonRadialStatusNode: ChatSendButtonRadialStatusNode?
@ -21,7 +23,9 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode {
} }
} }
init(theme: PresentationTheme, presentController: @escaping (ViewController) -> Void) { init(theme: PresentationTheme, strings: PresentationStrings, presentController: @escaping (ViewController) -> Void) {
self.strings = strings
self.micButton = ChatTextInputMediaRecordingButton(theme: theme, presentController: presentController) self.micButton = ChatTextInputMediaRecordingButton(theme: theme, presentController: presentController)
self.sendButton = HighlightTrackingButton() self.sendButton = HighlightTrackingButton()
self.sendButton.adjustsImageWhenHighlighted = false self.sendButton.adjustsImageWhenHighlighted = false
@ -126,15 +130,15 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode {
self.accessibilityTraits = .button self.accessibilityTraits = .button
switch self.micButton.mode { switch self.micButton.mode {
case .audio: case .audio:
self.accessibilityLabel = "Voice Message" self.accessibilityLabel = self.strings.VoiceOver_Chat_RecordModeVoiceMessage
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." self.accessibilityHint = self.strings.VoiceOver_Chat_RecordModeVoiceMessageInfo
case .video: case .video:
self.accessibilityLabel = "Video Message" self.accessibilityLabel = self.strings.VoiceOver_Chat_RecordModeVideoMessage
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." self.accessibilityHint = self.strings.VoiceOver_Chat_RecordModeVideoMessageInfo
} }
} else { } else {
self.accessibilityTraits = .button self.accessibilityTraits = .button
self.accessibilityLabel = "Send" self.accessibilityLabel = self.strings.MediaPicker_Send
self.accessibilityHint = nil self.accessibilityHint = nil
} }
} }

View File

@ -267,13 +267,50 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
} }
} }
func updateInputTextState(_ state: ChatTextInputState, keepSendButtonEnabled: Bool, extendedSearchLayout: Bool, animated: Bool) { func updateInputTextState(_ state: ChatTextInputState, keepSendButtonEnabled: Bool, extendedSearchLayout: Bool, accessoryItems: [ChatTextInputAccessoryItem], animated: Bool) {
if state.inputText.length != 0 && self.textInputNode == nil { if state.inputText.length != 0 && self.textInputNode == nil {
self.loadTextInputNode() self.loadTextInputNode()
} }
if let textInputNode = self.textInputNode { if let textInputNode = self.textInputNode, let currentState = self.presentationInterfaceState {
self.updatingInputState = true self.updatingInputState = true
var updateAccessoryButtons = false
if accessoryItems.count == self.accessoryItemButtons.count {
for i in 0 ..< accessoryItems.count {
if accessoryItems[i] != self.accessoryItemButtons[i].0 {
updateAccessoryButtons = true
break
}
}
} else {
updateAccessoryButtons = true
}
if updateAccessoryButtons {
var updatedButtons: [(ChatTextInputAccessoryItem, AccessoryItemIconButton)] = []
for item in accessoryItems {
var itemAndButton: (ChatTextInputAccessoryItem, AccessoryItemIconButton)?
for i in 0 ..< self.accessoryItemButtons.count {
if self.accessoryItemButtons[i].0 == item {
itemAndButton = self.accessoryItemButtons[i]
self.accessoryItemButtons.remove(at: i)
break
}
}
if itemAndButton == nil {
let button = AccessoryItemIconButton(item: item, theme: currentState.theme, strings: currentState.strings)
button.addTarget(self, action: #selector(self.accessoryItemButtonPressed(_:)), for: [.touchUpInside])
itemAndButton = (item, button)
}
updatedButtons.append(itemAndButton!)
}
for (_, button) in self.accessoryItemButtons {
button.removeFromSuperview()
}
self.accessoryItemButtons = updatedButtons
}
var textColor: UIColor = .black var textColor: UIColor = .black
var accentTextColor: UIColor = .blue var accentTextColor: UIColor = .blue
var baseFontSize: CGFloat = 17.0 var baseFontSize: CGFloat = 17.0
@ -335,14 +372,14 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
self.textPlaceholderNode.maximumNumberOfLines = 1 self.textPlaceholderNode.maximumNumberOfLines = 1
self.textPlaceholderNode.isUserInteractionEnabled = false self.textPlaceholderNode.isUserInteractionEnabled = false
self.attachmentButton = HighlightableButtonNode() self.attachmentButton = HighlightableButtonNode()
self.attachmentButton.accessibilityLabel = "Send media" self.attachmentButton.accessibilityLabel = presentationInterfaceState.strings.VoiceOver_AttachMedia
self.attachmentButton.isAccessibilityElement = true self.attachmentButton.isAccessibilityElement = true
self.attachmentButtonDisabledNode = HighlightableButtonNode() self.attachmentButtonDisabledNode = HighlightableButtonNode()
self.searchLayoutClearButton = HighlightableButton() self.searchLayoutClearButton = HighlightableButton()
self.searchLayoutProgressView = UIImageView(image: searchLayoutProgressImage) self.searchLayoutProgressView = UIImageView(image: searchLayoutProgressImage)
self.searchLayoutProgressView.isHidden = true self.searchLayoutProgressView.isHidden = true
self.actionButtons = ChatTextInputActionButtonsNode(theme: presentationInterfaceState.theme, presentController: presentController) self.actionButtons = ChatTextInputActionButtonsNode(theme: presentationInterfaceState.theme, strings: presentationInterfaceState.strings, presentController: presentController)
super.init() super.init()
@ -551,7 +588,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
let textFieldHeight: CGFloat let textFieldHeight: CGFloat
if let textInputNode = self.textInputNode { if let textInputNode = self.textInputNode {
let unboundTextFieldHeight = max(textFieldMinHeight, ceil(textInputNode.measure(CGSize(width: width - textFieldInsets.left - textFieldInsets.right - self.textInputViewInternalInsets.left - self.textInputViewInternalInsets.right - accessoryButtonsWidth, height: CGFloat.greatestFiniteMagnitude)).height)) let measuredHeight = textInputNode.measure(CGSize(width: width - textFieldInsets.left - textFieldInsets.right - self.textInputViewInternalInsets.left - self.textInputViewInternalInsets.right - accessoryButtonsWidth, height: CGFloat.greatestFiniteMagnitude))
let unboundTextFieldHeight = max(textFieldMinHeight, ceil(measuredHeight.height))
let maxNumberOfLines = min(12, (Int(fieldMaxHeight - 11.0) - 33) / 22) let maxNumberOfLines = min(12, (Int(fieldMaxHeight - 11.0) - 33) / 22)

View File

@ -41,7 +41,7 @@ private enum ConfirmPhoneNumberCodeTag: ItemListItemTag {
} }
private enum ConfirmPhoneNumberCodeEntry: ItemListNodeEntry { private enum ConfirmPhoneNumberCodeEntry: ItemListNodeEntry {
case codeEntry(PresentationTheme, String, String) case codeEntry(PresentationTheme, PresentationStrings, String, String)
case codeInfo(PresentationTheme, PresentationStrings, String, String) case codeInfo(PresentationTheme, PresentationStrings, String, String)
var section: ItemListSectionId { var section: ItemListSectionId {
@ -59,8 +59,8 @@ private enum ConfirmPhoneNumberCodeEntry: ItemListNodeEntry {
static func ==(lhs: ConfirmPhoneNumberCodeEntry, rhs: ConfirmPhoneNumberCodeEntry) -> Bool { static func ==(lhs: ConfirmPhoneNumberCodeEntry, rhs: ConfirmPhoneNumberCodeEntry) -> Bool {
switch lhs { switch lhs {
case let .codeEntry(lhsTheme, lhsTitle, lhsText): case let .codeEntry(lhsTheme, lhsStrings, lhsTitle, lhsText):
if case let .codeEntry(rhsTheme, rhsTitle, rhsText) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsText == rhsText { if case let .codeEntry(rhsTheme, rhsStrings, rhsTitle, rhsText) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsTitle == rhsTitle, lhsText == rhsText {
return true return true
} else { } else {
return false return false
@ -80,8 +80,8 @@ private enum ConfirmPhoneNumberCodeEntry: ItemListNodeEntry {
func item(_ arguments: ConfirmPhoneNumberCodeControllerArguments) -> ListViewItem { func item(_ arguments: ConfirmPhoneNumberCodeControllerArguments) -> ListViewItem {
switch self { switch self {
case let .codeEntry(theme, title, text): case let .codeEntry(theme, strings, title, text):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(string: title, textColor: .black), text: text, placeholder: "", type: .number, spacing: 10.0, tag: ConfirmPhoneNumberCodeTag.input, sectionId: self.section, textUpdated: { updatedText in return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(string: title, textColor: .black), text: text, placeholder: "", type: .number, spacing: 10.0, tag: ConfirmPhoneNumberCodeTag.input, sectionId: self.section, textUpdated: { updatedText in
arguments.updateEntryText(updatedText) arguments.updateEntryText(updatedText)
}, action: { }, action: {
arguments.next() arguments.next()
@ -118,7 +118,7 @@ private struct ConfirmPhoneNumberCodeControllerState: Equatable {
private func confirmPhoneNumberCodeControllerEntries(presentationData: PresentationData, state: ConfirmPhoneNumberCodeControllerState, phoneNumber: String, codeData: CancelAccountResetData, timeout: Int32?, strings: PresentationStrings, theme: PresentationTheme) -> [ConfirmPhoneNumberCodeEntry] { private func confirmPhoneNumberCodeControllerEntries(presentationData: PresentationData, state: ConfirmPhoneNumberCodeControllerState, phoneNumber: String, codeData: CancelAccountResetData, timeout: Int32?, strings: PresentationStrings, theme: PresentationTheme) -> [ConfirmPhoneNumberCodeEntry] {
var entries: [ConfirmPhoneNumberCodeEntry] = [] var entries: [ConfirmPhoneNumberCodeEntry] = []
entries.append(.codeEntry(presentationData.theme, presentationData.strings.ChangePhoneNumberCode_CodePlaceholder, state.codeText)) entries.append(.codeEntry(presentationData.theme, presentationData.strings, presentationData.strings.ChangePhoneNumberCode_CodePlaceholder, state.codeText))
var text = "" var text = ""
if let nextType = codeData.nextType { if let nextType = codeData.nextType {
text += authorizationNextOptionText(currentType: codeData.type, nextType: nextType, timeout: timeout, strings: presentationData.strings, primaryColor: .black, accentColor: .black).0.string text += authorizationNextOptionText(currentType: codeData.type, nextType: nextType, timeout: timeout, strings: presentationData.strings, primaryColor: .black, accentColor: .black).0.string

View File

@ -52,16 +52,16 @@ private enum CreatePasswordEntryTag: ItemListItemTag {
private enum CreatePasswordEntry: ItemListNodeEntry, Equatable { private enum CreatePasswordEntry: ItemListNodeEntry, Equatable {
case passwordHeader(PresentationTheme, String) case passwordHeader(PresentationTheme, String)
case password(PresentationTheme, String, String) case password(PresentationTheme, PresentationStrings, String, String)
case passwordConfirmation(PresentationTheme, String, String) case passwordConfirmation(PresentationTheme, PresentationStrings, String, String)
case passwordInfo(PresentationTheme, String) case passwordInfo(PresentationTheme, String)
case hintHeader(PresentationTheme, String) case hintHeader(PresentationTheme, String)
case hint(PresentationTheme, String, String, Bool) case hint(PresentationTheme, PresentationStrings, String, String, Bool)
case hintInfo(PresentationTheme, String) case hintInfo(PresentationTheme, String)
case emailHeader(PresentationTheme, String) case emailHeader(PresentationTheme, String)
case email(PresentationTheme, String, String) case email(PresentationTheme, PresentationStrings, String, String)
case emailInfo(PresentationTheme, String) case emailInfo(PresentationTheme, String)
case emailConfirmation(PresentationTheme, String) case emailConfirmation(PresentationTheme, String)
@ -117,14 +117,14 @@ private enum CreatePasswordEntry: ItemListNodeEntry, Equatable {
switch self { switch self {
case let .passwordHeader(theme, text): case let .passwordHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .password(theme, text, value): case let .password(theme, strings, text, value):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(), text: value, placeholder: text, type: .password, returnKeyType: .next, spacing: 0.0, tag: CreatePasswordEntryTag.password, sectionId: self.section, textUpdated: { updatedText in return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(), text: value, placeholder: text, type: .password, returnKeyType: .next, spacing: 0.0, tag: CreatePasswordEntryTag.password, sectionId: self.section, textUpdated: { updatedText in
arguments.updateFieldText(.password, updatedText) arguments.updateFieldText(.password, updatedText)
}, action: { }, action: {
arguments.selectNextInputItem(CreatePasswordEntryTag.password) arguments.selectNextInputItem(CreatePasswordEntryTag.password)
}) })
case let .passwordConfirmation(theme, text, value): case let .passwordConfirmation(theme, strings, text, value):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(), text: value, placeholder: text, type: .password, returnKeyType: .next, spacing: 0.0, tag: CreatePasswordEntryTag.passwordConfirmation, sectionId: self.section, textUpdated: { updatedText in return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(), text: value, placeholder: text, type: .password, returnKeyType: .next, spacing: 0.0, tag: CreatePasswordEntryTag.passwordConfirmation, sectionId: self.section, textUpdated: { updatedText in
arguments.updateFieldText(.passwordConfirmation, updatedText) arguments.updateFieldText(.passwordConfirmation, updatedText)
}, action: { }, action: {
arguments.selectNextInputItem(CreatePasswordEntryTag.passwordConfirmation) arguments.selectNextInputItem(CreatePasswordEntryTag.passwordConfirmation)
@ -133,8 +133,8 @@ private enum CreatePasswordEntry: ItemListNodeEntry, Equatable {
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section) return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
case let .hintHeader(theme, text): case let .hintHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .hint(theme, text, value, last): case let .hint(theme, strings, text, value, last):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(), text: value, placeholder: text, type: .regular(capitalization: true, autocorrection: false), returnKeyType: last ? .done : .next, spacing: 0.0, tag: CreatePasswordEntryTag.hint, sectionId: self.section, textUpdated: { updatedText in return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(), text: value, placeholder: text, type: .regular(capitalization: true, autocorrection: false), returnKeyType: last ? .done : .next, spacing: 0.0, tag: CreatePasswordEntryTag.hint, sectionId: self.section, textUpdated: { updatedText in
arguments.updateFieldText(.hint, updatedText) arguments.updateFieldText(.hint, updatedText)
}, action: { }, action: {
if last { if last {
@ -147,8 +147,8 @@ private enum CreatePasswordEntry: ItemListNodeEntry, Equatable {
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section) return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
case let .emailHeader(theme, text): case let .emailHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .email(theme, text, value): case let .email(theme, strings, text, value):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(), text: value, placeholder: text, type: .email, returnKeyType: .done, spacing: 0.0, tag: CreatePasswordEntryTag.email, sectionId: self.section, textUpdated: { updatedText in return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(), text: value, placeholder: text, type: .email, returnKeyType: .done, spacing: 0.0, tag: CreatePasswordEntryTag.email, sectionId: self.section, textUpdated: { updatedText in
arguments.updateFieldText(.email, updatedText) arguments.updateFieldText(.email, updatedText)
}, action: { }, action: {
arguments.save() arguments.save()
@ -184,8 +184,8 @@ private func createPasswordControllerEntries(presentationData: PresentationData,
switch state.state { switch state.state {
case let .setup(currentPassword): case let .setup(currentPassword):
entries.append(.passwordHeader(presentationData.theme, presentationData.strings.FastTwoStepSetup_PasswordSection)) entries.append(.passwordHeader(presentationData.theme, presentationData.strings.FastTwoStepSetup_PasswordSection))
entries.append(.password(presentationData.theme, presentationData.strings.FastTwoStepSetup_PasswordPlaceholder, state.passwordText)) entries.append(.password(presentationData.theme, presentationData.strings, presentationData.strings.FastTwoStepSetup_PasswordPlaceholder, state.passwordText))
entries.append(.passwordConfirmation(presentationData.theme, presentationData.strings.FastTwoStepSetup_PasswordConfirmationPlaceholder, state.passwordConfirmationText)) entries.append(.passwordConfirmation(presentationData.theme, presentationData.strings, presentationData.strings.FastTwoStepSetup_PasswordConfirmationPlaceholder, state.passwordConfirmationText))
if case .paymentInfo = context { if case .paymentInfo = context {
entries.append(.passwordInfo(presentationData.theme, presentationData.strings.FastTwoStepSetup_PasswordHelp)) entries.append(.passwordInfo(presentationData.theme, presentationData.strings.FastTwoStepSetup_PasswordHelp))
@ -194,12 +194,12 @@ private func createPasswordControllerEntries(presentationData: PresentationData,
let showEmail = currentPassword == nil let showEmail = currentPassword == nil
entries.append(.hintHeader(presentationData.theme, presentationData.strings.FastTwoStepSetup_HintSection)) entries.append(.hintHeader(presentationData.theme, presentationData.strings.FastTwoStepSetup_HintSection))
entries.append(.hint(presentationData.theme, presentationData.strings.FastTwoStepSetup_HintPlaceholder, state.hintText, !showEmail)) entries.append(.hint(presentationData.theme, presentationData.strings, presentationData.strings.FastTwoStepSetup_HintPlaceholder, state.hintText, !showEmail))
entries.append(.hintInfo(presentationData.theme, presentationData.strings.FastTwoStepSetup_HintHelp)) entries.append(.hintInfo(presentationData.theme, presentationData.strings.FastTwoStepSetup_HintHelp))
if showEmail { if showEmail {
entries.append(.emailHeader(presentationData.theme, presentationData.strings.FastTwoStepSetup_EmailSection)) entries.append(.emailHeader(presentationData.theme, presentationData.strings.FastTwoStepSetup_EmailSection))
entries.append(.email(presentationData.theme, presentationData.strings.FastTwoStepSetup_EmailPlaceholder, state.emailText)) entries.append(.email(presentationData.theme, presentationData.strings, presentationData.strings.FastTwoStepSetup_EmailPlaceholder, state.emailText))
entries.append(.emailInfo(presentationData.theme, presentationData.strings.FastTwoStepSetup_EmailHelp)) entries.append(.emailInfo(presentationData.theme, presentationData.strings.FastTwoStepSetup_EmailHelp))
} }
case let .pendingVerification(emailPattern): case let .pendingVerification(emailPattern):

View File

@ -42,20 +42,28 @@ private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode {
private let avatarNode: AvatarNode private let avatarNode: AvatarNode
private let textNode: ImmediateTextNode private let textNode: ImmediateTextNode
private let accessibilityArea: AccessibilityAreaNode
init(theme: ActionSheetControllerTheme, strings: PresentationStrings, context: AccountContextImpl, peer: Peer, chatPeer: Peer, action: DeleteChatPeerAction) { init(theme: ActionSheetControllerTheme, strings: PresentationStrings, context: AccountContextImpl, peer: Peer, chatPeer: Peer, action: DeleteChatPeerAction) {
self.theme = theme self.theme = theme
self.strings = strings self.strings = strings
self.avatarNode = AvatarNode(font: avatarFont) self.avatarNode = AvatarNode(font: avatarFont)
self.avatarNode.isAccessibilityElement = false
self.textNode = ImmediateTextNode() self.textNode = ImmediateTextNode()
self.textNode.displaysAsynchronously = false self.textNode.displaysAsynchronously = false
self.textNode.maximumNumberOfLines = 0 self.textNode.maximumNumberOfLines = 0
self.textNode.textAlignment = .center self.textNode.textAlignment = .center
self.textNode.isAccessibilityElement = false
self.accessibilityArea = AccessibilityAreaNode()
super.init(theme: theme) super.init(theme: theme)
self.addSubnode(self.avatarNode) self.addSubnode(self.avatarNode)
self.addSubnode(self.textNode) self.addSubnode(self.textNode)
self.addSubnode(self.accessibilityArea)
if chatPeer.id == context.account.peerId { if chatPeer.id == context.account.peerId {
self.avatarNode.setPeer(account: context.account, theme: (context.sharedContext.currentPresentationData.with { $0 }).theme, peer: peer, overrideImage: .savedMessagesIcon) self.avatarNode.setPeer(account: context.account, theme: (context.sharedContext.currentPresentationData.with { $0 }).theme, peer: peer, overrideImage: .savedMessagesIcon)
@ -88,6 +96,9 @@ private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode {
} }
self.textNode.attributedText = attributedText self.textNode.attributedText = attributedText
self.accessibilityArea.accessibilityLabel = attributedText.string
self.accessibilityArea.accessibilityTraits = .staticText
} }
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
@ -101,7 +112,10 @@ private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode {
self.avatarNode.frame = CGRect(origin: CGPoint(x: floor((constrainedSize.width - avatarSize) / 2.0), y: topInset), size: CGSize(width: avatarSize, height: avatarSize)) self.avatarNode.frame = CGRect(origin: CGPoint(x: floor((constrainedSize.width - avatarSize) / 2.0), y: topInset), size: CGSize(width: avatarSize, height: avatarSize))
self.textNode.frame = CGRect(origin: CGPoint(x: floor((constrainedSize.width - textSize.width) / 2.0), y: topInset + avatarSize + textSpacing), size: textSize) self.textNode.frame = CGRect(origin: CGPoint(x: floor((constrainedSize.width - textSize.width) / 2.0), y: topInset + avatarSize + textSpacing), size: textSize)
return CGSize(width: constrainedSize.width, height: topInset + avatarSize + textSpacing + textSize.height + bottomInset) let size = CGSize(width: constrainedSize.width, height: topInset + avatarSize + textSpacing + textSize.height + bottomInset)
self.accessibilityArea.frame = CGRect(origin: CGPoint(), size: size)
return size
} }
override func layout() { override func layout() {

View File

@ -67,7 +67,7 @@ final class EditAccessoryPanelNode: AccessoryPanelNode {
self.nameDisplayOrder = nameDisplayOrder self.nameDisplayOrder = nameDisplayOrder
self.closeButton = ASButtonNode() self.closeButton = ASButtonNode()
self.closeButton.accessibilityLabel = "Discard" self.closeButton.accessibilityLabel = strings.VoiceOver_DiscardPreparedContent
self.closeButton.setImage(PresentationResourcesChat.chatInputPanelCloseIconImage(theme), for: []) self.closeButton.setImage(PresentationResourcesChat.chatInputPanelCloseIconImage(theme), for: [])
self.closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0) self.closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0)
self.closeButton.displaysAsynchronously = false self.closeButton.displaysAsynchronously = false

View File

@ -83,7 +83,7 @@ final class ForwardAccessoryPanelNode: AccessoryPanelNode {
self.theme = theme self.theme = theme
self.closeButton = ASButtonNode() self.closeButton = ASButtonNode()
self.closeButton.accessibilityLabel = "Discard" self.closeButton.accessibilityLabel = strings.VoiceOver_DiscardPreparedContent
self.closeButton.setImage(PresentationResourcesChat.chatInputPanelCloseIconImage(theme), for: []) self.closeButton.setImage(PresentationResourcesChat.chatInputPanelCloseIconImage(theme), for: [])
self.closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0) self.closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0)
self.closeButton.displaysAsynchronously = false self.closeButton.displaysAsynchronously = false

View File

@ -62,7 +62,7 @@ private enum GroupStickerPackEntryId: Hashable {
} }
private enum GroupStickerPackEntry: ItemListNodeEntry { private enum GroupStickerPackEntry: ItemListNodeEntry {
case search(PresentationTheme, String, String, String) case search(PresentationTheme, PresentationStrings, String, String, String)
case currentPack(Int32, PresentationTheme, PresentationStrings, GroupStickerPackCurrentItemContent) case currentPack(Int32, PresentationTheme, PresentationStrings, GroupStickerPackCurrentItemContent)
case searchInfo(PresentationTheme, String) case searchInfo(PresentationTheme, String)
case packsTitle(PresentationTheme, String) case packsTitle(PresentationTheme, String)
@ -94,8 +94,8 @@ private enum GroupStickerPackEntry: ItemListNodeEntry {
static func ==(lhs: GroupStickerPackEntry, rhs: GroupStickerPackEntry) -> Bool { static func ==(lhs: GroupStickerPackEntry, rhs: GroupStickerPackEntry) -> Bool {
switch lhs { switch lhs {
case let .search(lhsTheme, lhsPrefix, lhsPlaceholder, lhsValue): case let .search(lhsTheme, lhsStrings, lhsPrefix, lhsPlaceholder, lhsValue):
if case let .search(rhsTheme, rhsPrefix, rhsPlaceholder, rhsValue) = rhs, lhsTheme === rhsTheme, lhsPrefix == rhsPrefix, lhsPlaceholder == rhsPlaceholder, lhsValue == rhsValue { if case let .search(rhsTheme, rhsStrings, rhsPrefix, rhsPlaceholder, rhsValue) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsPrefix == rhsPrefix, lhsPlaceholder == rhsPlaceholder, lhsValue == rhsValue {
return true return true
} else { } else {
return false return false
@ -205,8 +205,8 @@ private enum GroupStickerPackEntry: ItemListNodeEntry {
func item(_ arguments: GroupStickerPackSetupControllerArguments) -> ListViewItem { func item(_ arguments: GroupStickerPackSetupControllerArguments) -> ListViewItem {
switch self { switch self {
case let .search(theme, prefix, placeholder, value): case let .search(theme, strings, prefix, placeholder, value):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(string: prefix, textColor: theme.list.itemPrimaryTextColor), text: value, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: false), spacing: 0.0, clearButton: true, tag: nil, sectionId: self.section, textUpdated: { value in return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(string: prefix, textColor: theme.list.itemPrimaryTextColor), text: value, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: false), spacing: 0.0, clearButton: true, tag: nil, sectionId: self.section, textUpdated: { value in
arguments.updateSearchText(value) arguments.updateSearchText(value)
}, processPaste: { text in }, processPaste: { text in
if let url = (URL(string: text) ?? URL(string: "http://" + text)), url.host == "t.me" || url.host == "telegram.me" { if let url = (URL(string: text) ?? URL(string: "http://" + text)), url.host == "t.me" || url.host == "telegram.me" {
@ -269,7 +269,7 @@ private func groupStickerPackSetupControllerEntries(presentationData: Presentati
} }
var entries: [GroupStickerPackEntry] = [] var entries: [GroupStickerPackEntry] = []
entries.append(.search(presentationData.theme, "t.me/addstickers/", presentationData.strings.Channel_Stickers_Placeholder, searchText)) entries.append(.search(presentationData.theme, presentationData.strings, "t.me/addstickers/", presentationData.strings.Channel_Stickers_Placeholder, searchText))
switch searchState { switch searchState {
case .none: case .none:
break break

View File

@ -54,6 +54,8 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
var currentExpandedDetails: [Int : Bool]? var currentExpandedDetails: [Int : Bool]?
var currentDetailsItems: [InstantPageDetailsItem] = [] var currentDetailsItems: [InstantPageDetailsItem] = []
var currentAccessibilityAreas: [AccessibilityAreaNode] = []
private var previousContentOffset: CGPoint? private var previousContentOffset: CGPoint?
private var isDeceleratingBecauseOfDragging = false private var isDeceleratingBecauseOfDragging = false
@ -428,12 +430,22 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.currentExpandedDetails = expandedDetails self.currentExpandedDetails = expandedDetails
} }
let accessibilityAreas = instantPageAccessibilityAreasFromLayout(currentLayout, boundingWidth: containerLayout.size.width)
self.currentLayout = currentLayout self.currentLayout = currentLayout
self.currentLayoutTiles = currentLayoutTiles self.currentLayoutTiles = currentLayoutTiles
self.currentLayoutItemsWithNodes = currentLayoutItemsWithNodes self.currentLayoutItemsWithNodes = currentLayoutItemsWithNodes
self.currentDetailsItems = currentDetailsItems self.currentDetailsItems = currentDetailsItems
self.distanceThresholdGroupCount = distanceThresholdGroupCount self.distanceThresholdGroupCount = distanceThresholdGroupCount
for areaNode in self.currentAccessibilityAreas {
areaNode.removeFromSupernode()
}
for areaNode in accessibilityAreas {
self.scrollNode.addSubnode(areaNode)
}
self.currentAccessibilityAreas = accessibilityAreas
self.scrollNode.view.contentSize = currentLayout.contentSize self.scrollNode.view.contentSize = currentLayout.contentSize
self.scrollNodeFooter.frame = CGRect(origin: CGPoint(x: 0.0, y: currentLayout.contentSize.height), size: CGSize(width: containerLayout.size.width, height: 2000.0)) self.scrollNodeFooter.frame = CGRect(origin: CGPoint(x: 0.0, y: currentLayout.contentSize.height), size: CGSize(width: containerLayout.size.width, height: 2000.0))
} }

View File

@ -1,5 +1,6 @@
import Foundation import Foundation
import UIKit import UIKit
import Display
final class InstantPageTile { final class InstantPageTile {
let frame: CGRect let frame: CGRect
@ -79,3 +80,17 @@ func instantPageTilesFromLayout(_ layout: InstantPageLayout, boundingWidth: CGFl
return lhs.frame.minY < rhs.frame.minY return lhs.frame.minY < rhs.frame.minY
}) })
} }
func instantPageAccessibilityAreasFromLayout(_ layout: InstantPageLayout, boundingWidth: CGFloat) -> [AccessibilityAreaNode] {
var result: [AccessibilityAreaNode] = []
for item in layout.items {
if let item = item as? InstantPageTextItem {
let itemNode = AccessibilityAreaNode()
itemNode.frame = item.frame
itemNode.accessibilityTraits = .staticText
itemNode.accessibilityLabel = item.attributedString.string
result.append(itemNode)
}
}
return result
}

View File

@ -108,6 +108,8 @@ class ItemListCallListItemNode: ListViewItemNode {
let titleNode: TextNode let titleNode: TextNode
var callNodes: [(TextNode, TextNode)] var callNodes: [(TextNode, TextNode)]
private let accessibilityArea: AccessibilityAreaNode
private var item: ItemListCallListItem? private var item: ItemListCallListItem?
override var canBeSelected: Bool { override var canBeSelected: Bool {
@ -127,12 +129,16 @@ class ItemListCallListItemNode: ListViewItemNode {
self.titleNode = TextNode() self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false self.titleNode.isUserInteractionEnabled = false
self.titleNode.isAccessibilityElement = false
self.callNodes = [] self.callNodes = []
self.accessibilityArea = AccessibilityAreaNode()
super.init(layerBacked: false, dynamicBounce: false) super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.titleNode) self.addSubnode(self.titleNode)
self.addSubnode(self.accessibilityArea)
} }
func asyncLayout() -> (_ item: ItemListCallListItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { func asyncLayout() -> (_ item: ItemListCallListItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
@ -295,6 +301,8 @@ class ItemListCallListItemNode: ListViewItemNode {
yOffset += layout.0.size.height + 12.0 yOffset += layout.0.size.height + 12.0
index += 1 index += 1
} }
strongSelf.accessibilityArea.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
} }
}) })
} }

View File

@ -16,6 +16,7 @@ enum ItemListSingleLineInputItemType: Equatable {
class ItemListSingleLineInputItem: ListViewItem, ItemListItem { class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
let theme: PresentationTheme let theme: PresentationTheme
let strings: PresentationStrings
let title: NSAttributedString let title: NSAttributedString
let text: String let text: String
let placeholder: String let placeholder: String
@ -32,8 +33,9 @@ class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
let updatedFocus: ((Bool) -> Void)? let updatedFocus: ((Bool) -> Void)?
let tag: ItemListItemTag? let tag: ItemListItemTag?
init(theme: PresentationTheme, title: NSAttributedString, text: String, placeholder: String, type: ItemListSingleLineInputItemType = .regular(capitalization: true, autocorrection: true), returnKeyType: UIReturnKeyType = .`default`, spacing: CGFloat = 0.0, clearButton: Bool = false, enabled: Bool = true, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> String)? = nil, updatedFocus: ((Bool) -> Void)? = nil, action: @escaping () -> Void) { init(theme: PresentationTheme, strings: PresentationStrings, title: NSAttributedString, text: String, placeholder: String, type: ItemListSingleLineInputItemType = .regular(capitalization: true, autocorrection: true), returnKeyType: UIReturnKeyType = .`default`, spacing: CGFloat = 0.0, clearButton: Bool = false, enabled: Bool = true, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> String)? = nil, updatedFocus: ((Bool) -> Void)? = nil, action: @escaping () -> Void) {
self.theme = theme self.theme = theme
self.strings = strings
self.title = title self.title = title
self.text = text self.text = text
self.placeholder = placeholder self.placeholder = placeholder
@ -123,7 +125,6 @@ class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDelegate, It
self.clearIconNode.displaysAsynchronously = false self.clearIconNode.displaysAsynchronously = false
self.clearButtonNode = HighlightableButtonNode() self.clearButtonNode = HighlightableButtonNode()
self.clearButtonNode.accessibilityLabel = "Clear Text"
super.init(layerBacked: false, dynamicBounce: false) super.init(layerBacked: false, dynamicBounce: false)
@ -321,6 +322,8 @@ class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDelegate, It
strongSelf.textNode.isUserInteractionEnabled = item.enabled strongSelf.textNode.isUserInteractionEnabled = item.enabled
strongSelf.textNode.alpha = item.enabled ? 1.0 : 0.4 strongSelf.textNode.alpha = item.enabled ? 1.0 : 0.4
strongSelf.clearButtonNode.accessibilityLabel = item.strings.VoiceOver_Editing_ClearText
} }
}) })
} }

View File

@ -177,14 +177,14 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollViewDeleg
switch voiceBaseRate { switch voiceBaseRate {
case .x1: case .x1:
self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateInactiveIcon(self.theme), for: []) self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateInactiveIcon(self.theme), for: [])
self.rateButton.accessibilityLabel = "Playback rate" self.rateButton.accessibilityLabel = self.strings.VoiceOver_Media_PlaybackRate
self.rateButton.accessibilityValue = "Normal" self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRateNormal
self.rateButton.accessibilityHint = "Double tap to change" self.rateButton.accessibilityHint = self.strings.VoiceOver_Media_PlaybackRateChange
case .x2: case .x2:
self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateActiveIcon(self.theme), for: []) self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateActiveIcon(self.theme), for: [])
self.rateButton.accessibilityLabel = "Playback rate" self.rateButton.accessibilityLabel = self.strings.VoiceOver_Media_PlaybackRate
self.rateButton.accessibilityValue = "Fast" self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRateFast
self.rateButton.accessibilityHint = "Double tap to change" self.rateButton.accessibilityHint = self.strings.VoiceOver_Media_PlaybackRateChange
} }
} }
} }
@ -224,7 +224,7 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollViewDeleg
self.rightMaskNode.image = maskImage self.rightMaskNode.image = maskImage
self.closeButton = HighlightableButtonNode() self.closeButton = HighlightableButtonNode()
self.closeButton.accessibilityLabel = "Stop playback" self.closeButton.accessibilityLabel = presentationData.strings.VoiceOver_Media_PlaybackStop
self.closeButton.setImage(PresentationResourcesRootController.navigationPlayerCloseButton(self.theme), for: []) self.closeButton.setImage(PresentationResourcesRootController.navigationPlayerCloseButton(self.theme), for: [])
self.closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0) self.closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0)
self.closeButton.contentEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 2.0) self.closeButton.contentEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 2.0)
@ -336,7 +336,7 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollViewDeleg
} }
strongSelf.actionPlayNode.isHidden = !paused strongSelf.actionPlayNode.isHidden = !paused
strongSelf.actionPauseNode.isHidden = paused strongSelf.actionPauseNode.isHidden = paused
strongSelf.actionButton.accessibilityLabel = paused ? "Play" : "Pause" strongSelf.actionButton.accessibilityLabel = paused ? strongSelf.strings.VoiceOver_Media_PlaybackPlay : strongSelf.strings.VoiceOver_Media_PlaybackPause
} }
} }
} }

View File

@ -111,6 +111,7 @@ final class PasscodeSetupControllerNode: ASDisplayNode {
switch self.mode { switch self.mode {
case .entry: case .entry:
self.modeButtonNode.isHidden = true self.modeButtonNode.isHidden = true
self.modeButtonNode.isAccessibilityElement = false
text = self.presentationData.strings.EnterPasscode_EnterPasscode text = self.presentationData.strings.EnterPasscode_EnterPasscode
case let .setup(change, _): case let .setup(change, _):
if change { if change {
@ -219,6 +220,9 @@ final class PasscodeSetupControllerNode: ASDisplayNode {
self.subtitleNode.isHidden = false self.subtitleNode.isHidden = false
self.subtitleNode.attributedText = NSAttributedString(string: self.presentationData.strings.PasscodeSettings_DoNotMatch, font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor) self.subtitleNode.attributedText = NSAttributedString(string: self.presentationData.strings.PasscodeSettings_DoNotMatch, font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
self.modeButtonNode.isHidden = false self.modeButtonNode.isHidden = false
self.modeButtonNode.isAccessibilityElement = true
UIAccessibility.post(notification: UIAccessibility.Notification.announcement, argument: self.presentationData.strings.PasscodeSettings_DoNotMatch)
if let validLayout = self.validLayout { if let validLayout = self.validLayout {
self.containerLayoutUpdated(validLayout.0, navigationBarHeight: validLayout.1, transition: .immediate) self.containerLayoutUpdated(validLayout.0, navigationBarHeight: validLayout.1, transition: .immediate)
@ -240,6 +244,9 @@ final class PasscodeSetupControllerNode: ASDisplayNode {
self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.EnterPasscode_RepeatNewPasscode, font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor) self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.EnterPasscode_RepeatNewPasscode, font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
self.subtitleNode.isHidden = true self.subtitleNode.isHidden = true
self.modeButtonNode.isHidden = true self.modeButtonNode.isHidden = true
self.modeButtonNode.isAccessibilityElement = false
UIAccessibility.post(notification: UIAccessibility.Notification.announcement, argument: self.presentationData.strings.EnterPasscode_RepeatNewPasscode)
if let validLayout = self.validLayout { if let validLayout = self.validLayout {
self.containerLayoutUpdated(validLayout.0, navigationBarHeight: validLayout.1, transition: .immediate) self.containerLayoutUpdated(validLayout.0, navigationBarHeight: validLayout.1, transition: .immediate)
@ -251,6 +258,8 @@ final class PasscodeSetupControllerNode: ASDisplayNode {
func activateInput() { func activateInput() {
self.inputFieldNode.activateInput() self.inputFieldNode.activateInput()
UIAccessibility.post(notification: UIAccessibility.Notification.announcement, argument: self.titleNode.attributedText?.string)
} }
func animateError() { func animateError() {

View File

@ -268,7 +268,7 @@ class PeerMediaCollectionControllerNode: ASDisplayNode {
self.addSubnode(selectionPanelBackgroundNode) self.addSubnode(selectionPanelBackgroundNode)
self.selectionPanelBackgroundNode = selectionPanelBackgroundNode self.selectionPanelBackgroundNode = selectionPanelBackgroundNode
let selectionPanel = ChatMessageSelectionInputPanelNode(theme: self.chatPresentationInterfaceState.theme) let selectionPanel = ChatMessageSelectionInputPanelNode(theme: self.chatPresentationInterfaceState.theme, strings: self.chatPresentationInterfaceState.strings)
selectionPanel.context = self.context selectionPanel.context = self.context
selectionPanel.backgroundColor = self.presentationData.theme.chat.inputPanel.panelBackgroundColor selectionPanel.backgroundColor = self.presentationData.theme.chat.inputPanel.panelBackgroundColor
selectionPanel.interfaceInteraction = self.interfaceInteraction selectionPanel.interfaceInteraction = self.interfaceInteraction

View File

@ -100,6 +100,8 @@ final class PrivacyIntroControllerNode: ViewControllerTracingNode {
self.textNode.attributedText = NSAttributedString(string: self.mode.text(strings: presentationData.strings), font: textFont, textColor: presentationData.theme.list.freeTextColor, paragraphAlignment: .center) self.textNode.attributedText = NSAttributedString(string: self.mode.text(strings: presentationData.strings), font: textFont, textColor: presentationData.theme.list.freeTextColor, paragraphAlignment: .center)
self.noticeNode.attributedText = NSAttributedString(string: self.mode.notice(strings: presentationData.strings), font: textFont, textColor: presentationData.theme.list.freeTextColor, paragraphAlignment: .center) self.noticeNode.attributedText = NSAttributedString(string: self.mode.notice(strings: presentationData.strings), font: textFont, textColor: presentationData.theme.list.freeTextColor, paragraphAlignment: .center)
self.buttonTextNode.attributedText = NSAttributedString(string: self.mode.buttonTitle(strings: presentationData.strings), font: buttonFont, textColor: presentationData.theme.list.itemAccentColor, paragraphAlignment: .center) self.buttonTextNode.attributedText = NSAttributedString(string: self.mode.buttonTitle(strings: presentationData.strings), font: buttonFont, textColor: presentationData.theme.list.itemAccentColor, paragraphAlignment: .center)
self.buttonTextNode.isAccessibilityElement = false
self.buttonNode.accessibilityLabel = self.buttonTextNode.attributedText?.string
self.buttonBackgroundNode.image = generateButtonImage(backgroundColor: presentationData.theme.list.itemBlocksBackgroundColor, borderColor: presentationData.theme.list.itemBlocksSeparatorColor, highlightColor: nil) self.buttonBackgroundNode.image = generateButtonImage(backgroundColor: presentationData.theme.list.itemBlocksBackgroundColor, borderColor: presentationData.theme.list.itemBlocksSeparatorColor, highlightColor: nil)
self.buttonHighlightedBackgroundNode.image = generateButtonImage(backgroundColor: presentationData.theme.list.itemBlocksBackgroundColor, borderColor: presentationData.theme.list.itemBlocksSeparatorColor, highlightColor: presentationData.theme.list.itemHighlightedBackgroundColor) self.buttonHighlightedBackgroundNode.image = generateButtonImage(backgroundColor: presentationData.theme.list.itemBlocksBackgroundColor, borderColor: presentationData.theme.list.itemBlocksSeparatorColor, highlightColor: presentationData.theme.list.itemHighlightedBackgroundColor)

View File

@ -54,13 +54,13 @@ private enum ProxySettingsEntry: ItemListNodeEntry {
case modeMtp(PresentationTheme, String, Bool) case modeMtp(PresentationTheme, String, Bool)
case connectionHeader(PresentationTheme, String) case connectionHeader(PresentationTheme, String)
case connectionServer(PresentationTheme, String, String) case connectionServer(PresentationTheme, PresentationStrings, String, String)
case connectionPort(PresentationTheme, String, String) case connectionPort(PresentationTheme, PresentationStrings, String, String)
case credentialsHeader(PresentationTheme, String) case credentialsHeader(PresentationTheme, String)
case credentialsUsername(PresentationTheme, String, String) case credentialsUsername(PresentationTheme, PresentationStrings, String, String)
case credentialsPassword(PresentationTheme, String, String) case credentialsPassword(PresentationTheme, PresentationStrings, String, String)
case credentialsSecret(PresentationTheme, String, String) case credentialsSecret(PresentationTheme, PresentationStrings, String, String)
case share(PresentationTheme, String, Bool) case share(PresentationTheme, String, Bool)
@ -138,16 +138,16 @@ private enum ProxySettingsEntry: ItemListNodeEntry {
}) })
case let .connectionHeader(theme, text): case let .connectionHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .connectionServer(theme, placeholder, text): case let .connectionServer(theme, strings, placeholder, text):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(), text: text, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: false), sectionId: self.section, textUpdated: { value in return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(), text: text, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: false), sectionId: self.section, textUpdated: { value in
arguments.updateState { current in arguments.updateState { current in
var state = current var state = current
state.host = value state.host = value
return state return state
} }
}, action: {}) }, action: {})
case let .connectionPort(theme, placeholder, text): case let .connectionPort(theme, strings, placeholder, text):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(), text: text, placeholder: placeholder, type: .number, sectionId: self.section, textUpdated: { value in return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(), text: text, placeholder: placeholder, type: .number, sectionId: self.section, textUpdated: { value in
arguments.updateState { current in arguments.updateState { current in
var state = current var state = current
state.port = value state.port = value
@ -156,24 +156,24 @@ private enum ProxySettingsEntry: ItemListNodeEntry {
}, action: {}) }, action: {})
case let .credentialsHeader(theme, text): case let .credentialsHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .credentialsUsername(theme, placeholder, text): case let .credentialsUsername(theme, strings, placeholder, text):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(), text: text, placeholder: placeholder, sectionId: self.section, textUpdated: { value in return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(), text: text, placeholder: placeholder, sectionId: self.section, textUpdated: { value in
arguments.updateState { current in arguments.updateState { current in
var state = current var state = current
state.username = value state.username = value
return state return state
} }
}, action: {}) }, action: {})
case let .credentialsPassword(theme, placeholder, text): case let .credentialsPassword(theme, strings, placeholder, text):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(), text: text, placeholder: placeholder, type: .password, sectionId: self.section, textUpdated: { value in return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(), text: text, placeholder: placeholder, type: .password, sectionId: self.section, textUpdated: { value in
arguments.updateState { current in arguments.updateState { current in
var state = current var state = current
state.password = value state.password = value
return state return state
} }
}, action: {}) }, action: {})
case let .credentialsSecret(theme, placeholder, text): case let .credentialsSecret(theme, strings, placeholder, text):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(), text: text, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: false), sectionId: self.section, textUpdated: { value in return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(), text: text, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: false), sectionId: self.section, textUpdated: { value in
arguments.updateState { current in arguments.updateState { current in
var state = current var state = current
state.secret = value state.secret = value
@ -229,17 +229,17 @@ private func proxyServerSettingsControllerEntries(presentationData: (theme: Pres
entries.append(.modeMtp(presentationData.theme, presentationData.strings.SocksProxySetup_ProxyTelegram, state.mode == .mtp)) entries.append(.modeMtp(presentationData.theme, presentationData.strings.SocksProxySetup_ProxyTelegram, state.mode == .mtp))
entries.append(.connectionHeader(presentationData.theme, presentationData.strings.SocksProxySetup_Connection.uppercased())) entries.append(.connectionHeader(presentationData.theme, presentationData.strings.SocksProxySetup_Connection.uppercased()))
entries.append(.connectionServer(presentationData.theme, presentationData.strings.SocksProxySetup_Hostname, state.host)) entries.append(.connectionServer(presentationData.theme, presentationData.strings, presentationData.strings.SocksProxySetup_Hostname, state.host))
entries.append(.connectionPort(presentationData.theme, presentationData.strings.SocksProxySetup_Port, state.port)) entries.append(.connectionPort(presentationData.theme, presentationData.strings, presentationData.strings.SocksProxySetup_Port, state.port))
switch state.mode { switch state.mode {
case .socks5: case .socks5:
entries.append(.credentialsHeader(presentationData.theme, presentationData.strings.SocksProxySetup_Credentials)) entries.append(.credentialsHeader(presentationData.theme, presentationData.strings.SocksProxySetup_Credentials))
entries.append(.credentialsUsername(presentationData.theme, presentationData.strings.SocksProxySetup_Username, state.username)) entries.append(.credentialsUsername(presentationData.theme, presentationData.strings, presentationData.strings.SocksProxySetup_Username, state.username))
entries.append(.credentialsPassword(presentationData.theme, presentationData.strings.SocksProxySetup_Password, state.password)) entries.append(.credentialsPassword(presentationData.theme, presentationData.strings, presentationData.strings.SocksProxySetup_Password, state.password))
case .mtp: case .mtp:
entries.append(.credentialsHeader(presentationData.theme, presentationData.strings.SocksProxySetup_RequiredCredentials)) entries.append(.credentialsHeader(presentationData.theme, presentationData.strings.SocksProxySetup_RequiredCredentials))
entries.append(.credentialsSecret(presentationData.theme, presentationData.strings.SocksProxySetup_SecretPlaceholder, state.secret)) entries.append(.credentialsSecret(presentationData.theme, presentationData.strings, presentationData.strings.SocksProxySetup_SecretPlaceholder, state.secret))
} }
entries.append(.share(presentationData.theme, presentationData.strings.Conversation_ContextMenuShare, state.isComplete)) entries.append(.share(presentationData.theme, presentationData.strings.Conversation_ContextMenuShare, state.isComplete))

View File

@ -188,7 +188,6 @@ class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode {
} }
} }
} }
self.infoButtonNode.accessibilityLabel = "Info"
self.infoButtonNode.addTarget(self, action: #selector(self.infoButtonPressed), forControlEvents: .touchUpInside) self.infoButtonNode.addTarget(self, action: #selector(self.infoButtonPressed), forControlEvents: .touchUpInside)
self.activateArea.activate = { [weak self] in self.activateArea.activate = { [weak self] in
@ -272,9 +271,10 @@ class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode {
strongSelf.item = item strongSelf.item = item
strongSelf.layoutParams = params strongSelf.layoutParams = params
strongSelf.infoButtonNode.accessibilityLabel = item.strings.Conversation_Info
strongSelf.activateArea.accessibilityLabel = "\(titleAttributedString.string)\n\(statusAttributedString.string)" strongSelf.activateArea.accessibilityLabel = "\(titleAttributedString.string)\n\(statusAttributedString.string)"
if item.active { if item.active {
strongSelf.activateArea.accessibilityValue = "Active" strongSelf.activateArea.accessibilityValue = item.strings.ProxyServer_VoiceOver_Active
} else { } else {
strongSelf.activateArea.accessibilityValue = "" strongSelf.activateArea.accessibilityValue = ""
} }

View File

@ -30,7 +30,7 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode {
self.theme = theme self.theme = theme
self.closeButton = ASButtonNode() self.closeButton = ASButtonNode()
self.closeButton.accessibilityLabel = "Discard" self.closeButton.accessibilityLabel = strings.VoiceOver_DiscardPreparedContent
self.closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0) self.closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0)
self.closeButton.setImage(PresentationResourcesChat.chatInputPanelCloseIconImage(theme), for: []) self.closeButton.setImage(PresentationResourcesChat.chatInputPanelCloseIconImage(theme), for: [])
self.closeButton.displaysAsynchronously = false self.closeButton.displaysAsynchronously = false

View File

@ -35,7 +35,7 @@ private enum ResetPasswordEntryTag: ItemListItemTag {
} }
private enum ResetPasswordEntry: ItemListNodeEntry, Equatable { private enum ResetPasswordEntry: ItemListNodeEntry, Equatable {
case code(PresentationTheme, String, String) case code(PresentationTheme, PresentationStrings, String, String)
case codeInfo(PresentationTheme, String) case codeInfo(PresentationTheme, String)
case helpInfo(PresentationTheme, String) case helpInfo(PresentationTheme, String)
@ -65,8 +65,8 @@ private enum ResetPasswordEntry: ItemListNodeEntry, Equatable {
func item(_ arguments: ResetPasswordControllerArguments) -> ListViewItem { func item(_ arguments: ResetPasswordControllerArguments) -> ListViewItem {
switch self { switch self {
case let .code(theme, text, value): case let .code(theme, strings, text, value):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(string: text), text: value, placeholder: "", type: .number, spacing: 10.0, tag: ResetPasswordEntryTag.code, sectionId: self.section, textUpdated: { updatedText in return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(string: text), text: value, placeholder: "", type: .number, spacing: 10.0, tag: ResetPasswordEntryTag.code, sectionId: self.section, textUpdated: { updatedText in
arguments.updateCodeText(updatedText) arguments.updateCodeText(updatedText)
}, action: { }, action: {
}) })
@ -90,7 +90,7 @@ private struct ResetPasswordControllerState: Equatable {
private func resetPasswordControllerEntries(presentationData: PresentationData, state: ResetPasswordControllerState, pattern: String) -> [ResetPasswordEntry] { private func resetPasswordControllerEntries(presentationData: PresentationData, state: ResetPasswordControllerState, pattern: String) -> [ResetPasswordEntry] {
var entries: [ResetPasswordEntry] = [] var entries: [ResetPasswordEntry] = []
entries.append(.code(presentationData.theme, presentationData.strings.TwoStepAuth_RecoveryCode, state.code)) entries.append(.code(presentationData.theme, presentationData.strings, presentationData.strings.TwoStepAuth_RecoveryCode, state.code))
entries.append(.codeInfo(presentationData.theme, presentationData.strings.TwoStepAuth_RecoveryCodeHelp)) entries.append(.codeInfo(presentationData.theme, presentationData.strings.TwoStepAuth_RecoveryCodeHelp))
let stringData = presentationData.strings.TwoStepAuth_RecoveryEmailUnavailable(pattern) let stringData = presentationData.strings.TwoStepAuth_RecoveryEmailUnavailable(pattern)

View File

@ -42,10 +42,10 @@ private enum TwoStepVerificationPasswordEntryTag: ItemListItemTag {
private enum TwoStepVerificationPasswordEntryEntry: ItemListNodeEntry { private enum TwoStepVerificationPasswordEntryEntry: ItemListNodeEntry {
case passwordEntryTitle(PresentationTheme, String) case passwordEntryTitle(PresentationTheme, String)
case passwordEntry(PresentationTheme, String) case passwordEntry(PresentationTheme, PresentationStrings, String)
case hintTitle(PresentationTheme, String) case hintTitle(PresentationTheme, String)
case hintEntry(PresentationTheme, String) case hintEntry(PresentationTheme, PresentationStrings, String)
case emailEntry(PresentationTheme, PresentationStrings, String) case emailEntry(PresentationTheme, PresentationStrings, String)
case emailInfo(PresentationTheme, String) case emailInfo(PresentationTheme, String)
@ -79,8 +79,8 @@ private enum TwoStepVerificationPasswordEntryEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .passwordEntry(lhsTheme, lhsText): case let .passwordEntry(lhsTheme, lhsStrings, lhsText):
if case let .passwordEntry(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { if case let .passwordEntry(rhsTheme, rhsStrings, rhsText) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsText == rhsText {
return true return true
} else { } else {
return false return false
@ -91,8 +91,8 @@ private enum TwoStepVerificationPasswordEntryEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .hintEntry(lhsTheme, lhsText): case let .hintEntry(lhsTheme, lhsStrings, lhsText):
if case let .hintEntry(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { if case let .hintEntry(rhsTheme, rhsStrings, rhsText) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsText == rhsText {
return true return true
} else { } else {
return false return false
@ -120,22 +120,22 @@ private enum TwoStepVerificationPasswordEntryEntry: ItemListNodeEntry {
switch self { switch self {
case let .passwordEntryTitle(theme, text): case let .passwordEntryTitle(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .passwordEntry(theme, text): case let .passwordEntry(theme, strings, text):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(string: "", textColor: .black), text: text, placeholder: "", type: .password, spacing: 0.0, tag: TwoStepVerificationPasswordEntryTag.input, sectionId: self.section, textUpdated: { updatedText in return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(string: "", textColor: .black), text: text, placeholder: "", type: .password, spacing: 0.0, tag: TwoStepVerificationPasswordEntryTag.input, sectionId: self.section, textUpdated: { updatedText in
arguments.updateEntryText(updatedText) arguments.updateEntryText(updatedText)
}, action: { }, action: {
arguments.next() arguments.next()
}) })
case let .hintTitle(theme, text): case let .hintTitle(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .hintEntry(theme, text): case let .hintEntry(theme, strings, text):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(string: "", textColor: .black), text: text, placeholder: "", type: .password, spacing: 0.0, tag: TwoStepVerificationPasswordEntryTag.input, sectionId: self.section, textUpdated: { updatedText in return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(string: "", textColor: .black), text: text, placeholder: "", type: .password, spacing: 0.0, tag: TwoStepVerificationPasswordEntryTag.input, sectionId: self.section, textUpdated: { updatedText in
arguments.updateEntryText(updatedText) arguments.updateEntryText(updatedText)
}, action: { }, action: {
arguments.next() arguments.next()
}) })
case let .emailEntry(theme, strings, text): case let .emailEntry(theme, strings, text):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(string: strings.TwoStepAuth_Email, textColor: .black), text: text, placeholder: "", type: .email, spacing: 10.0, tag: TwoStepVerificationPasswordEntryTag.input, sectionId: self.section, textUpdated: { updatedText in return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(string: strings.TwoStepAuth_Email, textColor: .black), text: text, placeholder: "", type: .email, spacing: 10.0, tag: TwoStepVerificationPasswordEntryTag.input, sectionId: self.section, textUpdated: { updatedText in
arguments.updateEntryText(updatedText) arguments.updateEntryText(updatedText)
}, action: { }, action: {
arguments.next() arguments.next()
@ -230,13 +230,13 @@ private func twoStepVerificationPasswordEntryControllerEntries(presentationData:
switch state.stage { switch state.stage {
case let .entry(text): case let .entry(text):
entries.append(.passwordEntryTitle(presentationData.theme, presentationData.strings.TwoStepAuth_SetupPasswordEnterPasswordNew)) entries.append(.passwordEntryTitle(presentationData.theme, presentationData.strings.TwoStepAuth_SetupPasswordEnterPasswordNew))
entries.append(.passwordEntry(presentationData.theme, text)) entries.append(.passwordEntry(presentationData.theme, presentationData.strings, text))
case let .reentry(_, text): case let .reentry(_, text):
entries.append(.passwordEntryTitle(presentationData.theme, presentationData.strings.TwoStepAuth_SetupPasswordConfirmPassword)) entries.append(.passwordEntryTitle(presentationData.theme, presentationData.strings.TwoStepAuth_SetupPasswordConfirmPassword))
entries.append(.passwordEntry(presentationData.theme, text)) entries.append(.passwordEntry(presentationData.theme, presentationData.strings, text))
case let .hint(_, text): case let .hint(_, text):
entries.append(.hintTitle(presentationData.theme, presentationData.strings.TwoStepAuth_SetupHint)) entries.append(.hintTitle(presentationData.theme, presentationData.strings.TwoStepAuth_SetupHint))
entries.append(.hintEntry(presentationData.theme, text)) entries.append(.hintEntry(presentationData.theme, presentationData.strings, text))
case let .email(_, _, text): case let .email(_, _, text):
entries.append(.emailEntry(presentationData.theme, presentationData.strings, text)) entries.append(.emailEntry(presentationData.theme, presentationData.strings, text))
entries.append(.emailInfo(presentationData.theme, presentationData.strings.TwoStepAuth_EmailHelp)) entries.append(.emailInfo(presentationData.theme, presentationData.strings.TwoStepAuth_EmailHelp))

View File

@ -44,7 +44,7 @@ private enum TwoStepVerificationResetTag: ItemListItemTag {
} }
private enum TwoStepVerificationResetEntry: ItemListNodeEntry { private enum TwoStepVerificationResetEntry: ItemListNodeEntry {
case codeEntry(PresentationTheme, String, String) case codeEntry(PresentationTheme, PresentationStrings, String, String)
case codeInfo(PresentationTheme, String) case codeInfo(PresentationTheme, String)
var section: ItemListSectionId { var section: ItemListSectionId {
@ -62,8 +62,8 @@ private enum TwoStepVerificationResetEntry: ItemListNodeEntry {
static func ==(lhs: TwoStepVerificationResetEntry, rhs: TwoStepVerificationResetEntry) -> Bool { static func ==(lhs: TwoStepVerificationResetEntry, rhs: TwoStepVerificationResetEntry) -> Bool {
switch lhs { switch lhs {
case let .codeEntry(lhsTheme, lhsPlaceholder, lhsText): case let .codeEntry(lhsTheme, lhsStrings, lhsPlaceholder, lhsText):
if case let .codeEntry(rhsTheme, rhsPlaceholder, rhsText) = rhs, lhsTheme === rhsTheme, lhsPlaceholder == rhsPlaceholder, lhsText == rhsText { if case let .codeEntry(rhsTheme, rhsStrings, rhsPlaceholder, rhsText) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsPlaceholder == rhsPlaceholder, lhsText == rhsText {
return true return true
} else { } else {
return false return false
@ -83,8 +83,8 @@ private enum TwoStepVerificationResetEntry: ItemListNodeEntry {
func item(_ arguments: TwoStepVerificationResetControllerArguments) -> ListViewItem { func item(_ arguments: TwoStepVerificationResetControllerArguments) -> ListViewItem {
switch self { switch self {
case let .codeEntry(theme, placeholder, text): case let .codeEntry(theme, strings, placeholder, text):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(string: placeholder, textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: "", type: .password, spacing: 10.0, tag: TwoStepVerificationResetTag.input, sectionId: self.section, textUpdated: { updatedText in return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(string: placeholder, textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: "", type: .password, spacing: 10.0, tag: TwoStepVerificationResetTag.input, sectionId: self.section, textUpdated: { updatedText in
arguments.updateEntryText(updatedText) arguments.updateEntryText(updatedText)
}, action: { }, action: {
arguments.next() arguments.next()
@ -127,7 +127,7 @@ private struct TwoStepVerificationResetControllerState: Equatable {
private func twoStepVerificationResetControllerEntries(presentationData: PresentationData, state: TwoStepVerificationResetControllerState, emailPattern: String) -> [TwoStepVerificationResetEntry] { private func twoStepVerificationResetControllerEntries(presentationData: PresentationData, state: TwoStepVerificationResetControllerState, emailPattern: String) -> [TwoStepVerificationResetEntry] {
var entries: [TwoStepVerificationResetEntry] = [] var entries: [TwoStepVerificationResetEntry] = []
entries.append(.codeEntry(presentationData.theme, presentationData.strings.TwoStepAuth_RecoveryCode, state.codeText)) entries.append(.codeEntry(presentationData.theme, presentationData.strings, presentationData.strings.TwoStepAuth_RecoveryCode, state.codeText))
entries.append(.codeInfo(presentationData.theme, "\(presentationData.strings.TwoStepAuth_RecoveryCodeHelp)\n\n[\(presentationData.strings.TwoStepAuth_RecoveryEmailUnavailable(escapedPlaintextForMarkdown(emailPattern)).0)]()")) entries.append(.codeInfo(presentationData.theme, "\(presentationData.strings.TwoStepAuth_RecoveryCodeHelp)\n\n[\(presentationData.strings.TwoStepAuth_RecoveryEmailUnavailable(escapedPlaintextForMarkdown(emailPattern)).0)]()"))
return entries return entries

View File

@ -57,7 +57,7 @@ private enum TwoStepVerificationUnlockSettingsEntryTag: ItemListItemTag {
} }
private enum TwoStepVerificationUnlockSettingsEntry: ItemListNodeEntry { private enum TwoStepVerificationUnlockSettingsEntry: ItemListNodeEntry {
case passwordEntry(PresentationTheme, String, String) case passwordEntry(PresentationTheme, PresentationStrings, String, String)
case passwordEntryInfo(PresentationTheme, String) case passwordEntryInfo(PresentationTheme, String)
case passwordSetup(PresentationTheme, String) case passwordSetup(PresentationTheme, String)
@ -69,7 +69,7 @@ private enum TwoStepVerificationUnlockSettingsEntry: ItemListNodeEntry {
case passwordInfo(PresentationTheme, String) case passwordInfo(PresentationTheme, String)
case pendingEmailConfirmInfo(PresentationTheme, String) case pendingEmailConfirmInfo(PresentationTheme, String)
case pendingEmailConfirmCode(PresentationTheme, String, String) case pendingEmailConfirmCode(PresentationTheme, PresentationStrings, String, String)
case pendingEmailInfo(PresentationTheme, String) case pendingEmailInfo(PresentationTheme, String)
case pendingEmailOpenConfirm(PresentationTheme, String) case pendingEmailOpenConfirm(PresentationTheme, String)
@ -117,8 +117,8 @@ private enum TwoStepVerificationUnlockSettingsEntry: ItemListNodeEntry {
func item(_ arguments: TwoStepVerificationUnlockSettingsControllerArguments) -> ListViewItem { func item(_ arguments: TwoStepVerificationUnlockSettingsControllerArguments) -> ListViewItem {
switch self { switch self {
case let .passwordEntry(theme, text, value): case let .passwordEntry(theme, strings, text, value):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(string: text, textColor: theme.list.itemPrimaryTextColor), text: value, placeholder: "", type: .password, spacing: 10.0, tag: TwoStepVerificationUnlockSettingsEntryTag.password, sectionId: self.section, textUpdated: { updatedText in return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(string: text, textColor: theme.list.itemPrimaryTextColor), text: value, placeholder: "", type: .password, spacing: 10.0, tag: TwoStepVerificationUnlockSettingsEntryTag.password, sectionId: self.section, textUpdated: { updatedText in
arguments.updatePasswordText(updatedText) arguments.updatePasswordText(updatedText)
}, action: { }, action: {
arguments.checkPassword() arguments.checkPassword()
@ -152,8 +152,8 @@ private enum TwoStepVerificationUnlockSettingsEntry: ItemListNodeEntry {
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section) return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
case let .pendingEmailConfirmInfo(theme, text): case let .pendingEmailConfirmInfo(theme, text):
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section) return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
case let .pendingEmailConfirmCode(theme, title, text): case let .pendingEmailConfirmCode(theme, strings, title, text):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(string: ""), text: text, placeholder: title, type: .number, sectionId: self.section, textUpdated: { value in return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(string: ""), text: text, placeholder: title, type: .number, sectionId: self.section, textUpdated: { value in
arguments.updateEmailCode(value) arguments.updateEmailCode(value)
}, action: {}) }, action: {})
case let .pendingEmailInfo(theme, text): case let .pendingEmailInfo(theme, text):
@ -187,7 +187,7 @@ private func twoStepVerificationUnlockSettingsControllerEntries(presentationData
case let .notSet(pendingEmail): case let .notSet(pendingEmail):
if let pendingEmail = pendingEmail { if let pendingEmail = pendingEmail {
entries.append(.pendingEmailConfirmInfo(presentationData.theme, presentationData.strings.TwoStepAuth_SetupPendingEmail(pendingEmail.email.pattern).0)) entries.append(.pendingEmailConfirmInfo(presentationData.theme, presentationData.strings.TwoStepAuth_SetupPendingEmail(pendingEmail.email.pattern).0))
entries.append(.pendingEmailConfirmCode(presentationData.theme, presentationData.strings.TwoStepAuth_RecoveryCode, state.emailCode)) entries.append(.pendingEmailConfirmCode(presentationData.theme, presentationData.strings, presentationData.strings.TwoStepAuth_RecoveryCode, state.emailCode))
entries.append(.pendingEmailInfo(presentationData.theme, "[" + presentationData.strings.TwoStepAuth_ConfirmationAbort + "]()")) entries.append(.pendingEmailInfo(presentationData.theme, "[" + presentationData.strings.TwoStepAuth_ConfirmationAbort + "]()"))
/*entries.append(.pendingEmailInfo(presentationData.theme, presentationData.strings.TwoStepAuth_ConfirmationText + "\n\n\(pendingEmailAndValue.pendingEmail.pattern)\n\n[" + presentationData.strings.TwoStepAuth_ConfirmationAbort + "]()"))*/ /*entries.append(.pendingEmailInfo(presentationData.theme, presentationData.strings.TwoStepAuth_ConfirmationText + "\n\n\(pendingEmailAndValue.pendingEmail.pattern)\n\n[" + presentationData.strings.TwoStepAuth_ConfirmationAbort + "]()"))*/
@ -196,7 +196,7 @@ private func twoStepVerificationUnlockSettingsControllerEntries(presentationData
entries.append(.passwordSetupInfo(presentationData.theme, presentationData.strings.TwoStepAuth_SetPasswordHelp)) entries.append(.passwordSetupInfo(presentationData.theme, presentationData.strings.TwoStepAuth_SetPasswordHelp))
} }
case let .set(hint, _, _): case let .set(hint, _, _):
entries.append(.passwordEntry(presentationData.theme, presentationData.strings.TwoStepAuth_EnterPasswordPassword, state.passwordText)) entries.append(.passwordEntry(presentationData.theme, presentationData.strings, presentationData.strings.TwoStepAuth_EnterPasswordPassword, state.passwordText))
if hint.isEmpty { if hint.isEmpty {
entries.append(.passwordEntryInfo(presentationData.theme, presentationData.strings.TwoStepAuth_EnterPasswordHelp + "\n\n[" + presentationData.strings.TwoStepAuth_EnterPasswordForgot + "](forgot)")) entries.append(.passwordEntryInfo(presentationData.theme, presentationData.strings.TwoStepAuth_EnterPasswordHelp + "\n\n[" + presentationData.strings.TwoStepAuth_EnterPasswordForgot + "](forgot)"))
} else { } else {

View File

@ -38,7 +38,7 @@ public enum UsernameEntryTag: ItemListItemTag {
private enum UsernameSetupEntry: ItemListNodeEntry { private enum UsernameSetupEntry: ItemListNodeEntry {
case editablePublicLink(PresentationTheme, String, String?, String) case editablePublicLink(PresentationTheme, PresentationStrings, String, String?, String)
case publicLinkStatus(PresentationTheme, String, AddressNameValidationStatus, String) case publicLinkStatus(PresentationTheme, String, AddressNameValidationStatus, String)
case publicLinkInfo(PresentationTheme, String) case publicLinkInfo(PresentationTheme, String)
@ -62,8 +62,8 @@ private enum UsernameSetupEntry: ItemListNodeEntry {
static func ==(lhs: UsernameSetupEntry, rhs: UsernameSetupEntry) -> Bool { static func ==(lhs: UsernameSetupEntry, rhs: UsernameSetupEntry) -> Bool {
switch lhs { switch lhs {
case let .editablePublicLink(lhsTheme, lhsPrefix, lhsCurrentText, lhsText): case let .editablePublicLink(lhsTheme, lhsStrings, lhsPrefix, lhsCurrentText, lhsText):
if case let .editablePublicLink(rhsTheme, rhsPrefix, rhsCurrentText, rhsText) = rhs, lhsTheme === rhsTheme, lhsPrefix == rhsPrefix, lhsCurrentText == rhsCurrentText, lhsText == rhsText { if case let .editablePublicLink(rhsTheme, rhsStrings, rhsPrefix, rhsCurrentText, rhsText) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsPrefix == rhsPrefix, lhsCurrentText == rhsCurrentText, lhsText == rhsText {
return true return true
} else { } else {
return false return false
@ -89,8 +89,8 @@ private enum UsernameSetupEntry: ItemListNodeEntry {
func item(_ arguments: UsernameSetupControllerArguments) -> ListViewItem { func item(_ arguments: UsernameSetupControllerArguments) -> ListViewItem {
switch self { switch self {
case let .editablePublicLink(theme, prefix, currentText, text): case let .editablePublicLink(theme, strings, prefix, currentText, text):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(string: prefix, textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: "", type: .username, spacing: 10.0, clearButton: true, tag: UsernameEntryTag.username, sectionId: self.section, textUpdated: { updatedText in return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(string: prefix, textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: "", type: .username, spacing: 10.0, clearButton: true, tag: UsernameEntryTag.username, sectionId: self.section, textUpdated: { updatedText in
arguments.updatePublicLinkText(currentText, updatedText) arguments.updatePublicLinkText(currentText, updatedText)
}, action: { }, action: {
}) })
@ -181,7 +181,7 @@ private func usernameSetupControllerEntries(presentationData: PresentationData,
} }
} }
entries.append(.editablePublicLink(presentationData.theme, presentationData.strings.Username_Title, peer.addressName, currentAddressName)) entries.append(.editablePublicLink(presentationData.theme, presentationData.strings, presentationData.strings.Username_Title, peer.addressName, currentAddressName))
if let status = state.addressNameValidationStatus { if let status = state.addressNameValidationStatus {
let statusText: String let statusText: String
switch status { switch status {

View File

@ -22,7 +22,7 @@ private enum WatchSettingsSection: Int32 {
private enum WatchSettingsControllerEntry: ItemListNodeEntry { private enum WatchSettingsControllerEntry: ItemListNodeEntry {
case replyPresetsHeader(PresentationTheme, String) case replyPresetsHeader(PresentationTheme, String)
case replyPreset(PresentationTheme, String, String, String, Int32) case replyPreset(PresentationTheme, PresentationStrings, String, String, String, Int32)
case replyPresetsInfo(PresentationTheme, String) case replyPresetsInfo(PresentationTheme, String)
var section: ItemListSectionId { var section: ItemListSectionId {
@ -36,7 +36,7 @@ private enum WatchSettingsControllerEntry: ItemListNodeEntry {
switch self { switch self {
case .replyPresetsHeader: case .replyPresetsHeader:
return 0 return 0
case let .replyPreset(_, _, _, _, index): case let .replyPreset(_, _, _, _, _, index):
return 1 + index return 1 + index
case .replyPresetsInfo: case .replyPresetsInfo:
return 100 return 100
@ -52,8 +52,8 @@ private enum WatchSettingsControllerEntry: ItemListNodeEntry {
return false return false
} }
case let .replyPreset(lhsTheme, lhsIdentifier, lhsPlaceholder, lhsValue, lhsIndex): case let .replyPreset(lhsTheme, lhsStrings, lhsIdentifier, lhsPlaceholder, lhsValue, lhsIndex):
if case let .replyPreset(rhsTheme, rhsIdentifier, rhsPlaceholder, rhsValue, rhsIndex) = rhs, lhsTheme === rhsTheme, lhsIdentifier == rhsIdentifier, lhsPlaceholder == rhsPlaceholder, lhsValue == rhsValue, lhsIndex == rhsIndex { if case let .replyPreset(rhsTheme, rhsStrings, rhsIdentifier, rhsPlaceholder, rhsValue, rhsIndex) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsIdentifier == rhsIdentifier, lhsPlaceholder == rhsPlaceholder, lhsValue == rhsValue, lhsIndex == rhsIndex {
return true return true
} else { } else {
return false return false
@ -76,8 +76,8 @@ private enum WatchSettingsControllerEntry: ItemListNodeEntry {
switch self { switch self {
case let .replyPresetsHeader(theme, text): case let .replyPresetsHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .replyPreset(theme, identifier, placeholder, value, _): case let .replyPreset(theme, strings, identifier, placeholder, value, _):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(string: ""), text: value, placeholder: placeholder, type: .regular(capitalization: true, autocorrection: true), spacing: 0.0, sectionId: self.section, textUpdated: { updatedText in return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(string: ""), text: value, placeholder: placeholder, type: .regular(capitalization: true, autocorrection: true), spacing: 0.0, sectionId: self.section, textUpdated: { updatedText in
arguments.updatePreset(identifier, updatedText.trimmingCharacters(in: .whitespacesAndNewlines)) arguments.updatePreset(identifier, updatedText.trimmingCharacters(in: .whitespacesAndNewlines))
}, action: {}) }, action: {})
case let .replyPresetsInfo(theme, text): case let .replyPresetsInfo(theme, text):
@ -102,7 +102,7 @@ private func watchSettingsControllerEntries(presentationData: PresentationData,
entries.append(.replyPresetsHeader(presentationData.theme, presentationData.strings.AppleWatch_ReplyPresets)) entries.append(.replyPresetsHeader(presentationData.theme, presentationData.strings.AppleWatch_ReplyPresets))
for (index, identifier, placeholder) in defaultSuggestions { for (index, identifier, placeholder) in defaultSuggestions {
entries.append(.replyPreset(presentationData.theme, identifier, placeholder, customPresets[identifier] ?? "", index)) entries.append(.replyPreset(presentationData.theme, presentationData.strings, identifier, placeholder, customPresets[identifier] ?? "", index))
} }
entries.append(.replyPresetsInfo(presentationData.theme, presentationData.strings.AppleWatch_ReplyPresetsHelp)) entries.append(.replyPresetsInfo(presentationData.theme, presentationData.strings.AppleWatch_ReplyPresetsHelp))