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"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
enableAddressSanitizer = "YES"
enableASanStackUseAfterReturn = "YES"
launchStyle = "0"
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.MessageRankUsername" = "changed custom title for %1$@ (%2$@):\n%3$@";
"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 var activate: (() -> Bool)?
public var focused: (() -> Void)?
override public init() {
super.init()
@ -18,4 +19,24 @@ public final class AccessibilityAreaNode: ASDisplayNode {
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
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 label: ASTextNode
private let accessibilityArea: AccessibilityAreaNode
override public init(theme: ActionSheetControllerTheme) {
self.theme = theme
self.button = HighlightTrackingButton()
self.button.isAccessibilityElement = false
self.label = ASTextNode()
self.label.isUserInteractionEnabled = false
@ -67,6 +69,8 @@ public class ActionSheetButtonNode: ActionSheetItemNode {
self.label.displaysAsynchronously = false
self.label.truncationMode = .byTruncatingTail
self.accessibilityArea = AccessibilityAreaNode()
super.init(theme: theme)
self.view.addSubview(self.button)
@ -74,6 +78,8 @@ public class ActionSheetButtonNode: ActionSheetItemNode {
self.label.isUserInteractionEnabled = false
self.addSubnode(self.label)
self.addSubnode(self.accessibilityArea)
self.button.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
@ -87,6 +93,10 @@ public class ActionSheetButtonNode: ActionSheetItemNode {
}
self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
self.accessibilityArea.activate = { [weak self] in
self?.buttonPressed()
return true
}
}
func setItem(_ item: ActionSheetButtonItem) {
@ -109,9 +119,18 @@ public class ActionSheetButtonNode: ActionSheetItemNode {
textFont = ActionSheetButtonNode.boldFont
}
self.label.attributedText = NSAttributedString(string: item.title, font: textFont, textColor: textColor)
self.label.isAccessibilityElement = false
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()
}
@ -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))
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() {

View File

@ -50,20 +50,25 @@ public class ActionSheetCheckboxItemNode: ActionSheetItemNode {
private let labelNode: ImmediateTextNode
private let checkNode: ASImageNode
private let accessibilityArea: AccessibilityAreaNode
override public init(theme: ActionSheetControllerTheme) {
self.theme = theme
self.button = HighlightTrackingButton()
self.button.isAccessibilityElement = false
self.titleNode = ImmediateTextNode()
self.titleNode.maximumNumberOfLines = 1
self.titleNode.isUserInteractionEnabled = false
self.titleNode.displaysAsynchronously = false
self.titleNode.isAccessibilityElement = false
self.labelNode = ImmediateTextNode()
self.labelNode.maximumNumberOfLines = 1
self.labelNode.isUserInteractionEnabled = false
self.labelNode.displaysAsynchronously = false
self.labelNode.isAccessibilityElement = false
self.checkNode = ASImageNode()
self.checkNode.isUserInteractionEnabled = false
@ -78,6 +83,9 @@ public class ActionSheetCheckboxItemNode: ActionSheetItemNode {
context.addLine(to: CGPoint(x: 1.0, y: 5.81145833))
context.strokePath()
})
self.checkNode.isAccessibilityElement = false
self.accessibilityArea = AccessibilityAreaNode()
super.init(theme: theme)
@ -85,6 +93,7 @@ public class ActionSheetCheckboxItemNode: ActionSheetItemNode {
self.addSubnode(self.titleNode)
self.addSubnode(self.labelNode)
self.addSubnode(self.checkNode)
self.addSubnode(self.accessibilityArea)
self.button.highligthedChanged = { [weak self] highlighted in
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)
}
@ -108,6 +122,14 @@ public class ActionSheetCheckboxItemNode: ActionSheetItemNode {
self.labelNode.attributedText = NSAttributedString(string: item.label, font: ActionSheetCheckboxItemNode.defaultFont, textColor: self.theme.secondaryTextColor)
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()
}
@ -137,6 +159,8 @@ public class ActionSheetCheckboxItemNode: ActionSheetItemNode {
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.accessibilityArea.frame = CGRect(origin: CGPoint(), size: size)
}
@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 class ActionSheetControllerNodeScrollView: UIScrollView {
override func touchesShouldCancel(in view: UIView) -> Bool {
return true
}
}
final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate {
var theme: ActionSheetControllerTheme {
didSet {
@ -26,6 +20,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate {
private let itemGroupsContainerNode: ActionSheetItemGroupsContainerNode
private let scrollNode: ASScrollNode
private let scrollView: UIScrollView
var dismiss: (Bool) -> Void = { _ in }
@ -35,7 +30,9 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate {
init(theme: ActionSheetControllerTheme) {
self.theme = theme
self.scrollView = ActionSheetControllerNodeScrollView()
self.scrollNode = ASScrollNode()
self.scrollNode.canCancelAllTouchesInViews = true
self.scrollView = self.scrollNode.view
if #available(iOSApplicationExtension 11.0, *) {
self.scrollView.contentInsetAdjustmentBehavior = .never
@ -64,7 +61,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.scrollView.delegate = self
self.view.addSubview(self.scrollView)
self.addSubnode(self.scrollNode)
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.scrollView.addSubnode(self.itemGroupsContainerNode)
self.scrollNode.addSubnode(self.itemGroupsContainerNode)
self.updateTheme()
}

View File

@ -1,12 +1,6 @@
import UIKit
import AsyncDisplayKit
private class ActionSheetItemGroupNodeScrollView: UIScrollView {
override func touchesShouldCancel(in view: UIView) -> Bool {
return true
}
}
final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate {
private let theme: ActionSheetControllerTheme
@ -17,7 +11,7 @@ final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate {
private let clippingNode: ASDisplayNode
private let backgroundEffectView: UIVisualEffectView
private let scrollView: UIScrollView
private let scrollNode: ASScrollNode
private var itemNodes: [ActionSheetItemNode] = []
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.scrollView = ActionSheetItemGroupNodeScrollView()
self.scrollNode = ASScrollNode()
self.scrollNode.canCancelAllTouchesInViews = true
if #available(iOSApplicationExtension 11.0, *) {
self.scrollView.contentInsetAdjustmentBehavior = .never
self.scrollNode.view.contentInsetAdjustmentBehavior = .never
}
self.scrollView.delaysContentTouches = false
self.scrollView.canCancelContentTouches = true
self.scrollView.showsVerticalScrollIndicator = false
self.scrollView.showsHorizontalScrollIndicator = false
self.scrollNode.view.delaysContentTouches = false
self.scrollNode.view.canCancelContentTouches = true
self.scrollNode.view.showsVerticalScrollIndicator = false
self.scrollNode.view.showsHorizontalScrollIndicator = false
super.init()
@ -63,10 +58,10 @@ final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate {
self.view.addSubview(self.bottomDimView)
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.scrollView)
self.clippingNode.addSubnode(self.scrollNode)
self.addSubnode(self.clippingNode)
}
@ -80,7 +75,7 @@ final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate {
for node in nodes {
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() {
let scrollViewFrame = CGRect(origin: CGPoint(), size: self.calculatedSize)
var updateOffset = false
if !self.scrollView.frame.equalTo(scrollViewFrame) {
self.scrollView.frame = scrollViewFrame
if !self.scrollNode.frame.equalTo(scrollViewFrame) {
self.scrollNode.frame = scrollViewFrame
updateOffset = true
}
@ -149,17 +144,17 @@ final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate {
}
let scrollViewContentSize = CGSize(width: self.calculatedSize.width, height: itemNodesHeight)
if !self.scrollView.contentSize.equalTo(scrollViewContentSize) {
self.scrollView.contentSize = scrollViewContentSize
if !self.scrollNode.view.contentSize.equalTo(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)
if !UIEdgeInsetsEqualToEdgeInsets(self.scrollView.contentInset, scrollViewContentInsets) {
self.scrollView.contentInset = scrollViewContentInsets
if self.scrollNode.view.contentInset != scrollViewContentInsets {
self.scrollNode.view.contentInset = scrollViewContentInsets
}
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()
@ -167,20 +162,20 @@ final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate {
private func currentVerticalOverscroll() -> CGFloat {
var verticalOverscroll: CGFloat = 0.0
if scrollView.contentOffset.y < 0.0 {
verticalOverscroll = scrollView.contentOffset.y
} else if scrollView.contentOffset.y > scrollView.contentSize.height - scrollView.bounds.size.height {
verticalOverscroll = scrollView.contentOffset.y - (scrollView.contentSize.height - scrollView.bounds.size.height)
if scrollNode.view.contentOffset.y < 0.0 {
verticalOverscroll = scrollNode.view.contentOffset.y
} else if scrollNode.view.contentOffset.y > scrollNode.view.contentSize.height - scrollNode.view.bounds.size.height {
verticalOverscroll = scrollNode.view.contentOffset.y - (scrollNode.view.contentSize.height - scrollNode.view.bounds.size.height)
}
return verticalOverscroll
}
private func currentRealVerticalOverscroll() -> CGFloat {
var verticalOverscroll: CGFloat = 0.0
if scrollView.contentOffset.y < 0.0 {
verticalOverscroll = scrollView.contentOffset.y
} else if scrollView.contentOffset.y > scrollView.contentSize.height - scrollView.bounds.size.height {
verticalOverscroll = scrollView.contentOffset.y - (scrollView.contentSize.height - scrollView.bounds.size.height)
if scrollNode.view.contentOffset.y < 0.0 {
verticalOverscroll = scrollNode.view.contentOffset.y
} else if scrollNode.view.contentOffset.y > scrollNode.view.contentSize.height - scrollNode.view.bounds.size.height {
verticalOverscroll = scrollNode.view.contentOffset.y - (scrollNode.view.contentSize.height - scrollNode.view.bounds.size.height)
}
return verticalOverscroll
}

View File

@ -38,21 +38,28 @@ public class ActionSheetSwitchNode: ActionSheetItemNode {
private let label: ASTextNode
private let switchNode: SwitchNode
private let accessibilityArea: AccessibilityAreaNode
override public init(theme: ActionSheetControllerTheme) {
self.theme = theme
self.button = HighlightTrackingButton()
self.button.isAccessibilityElement = false
self.label = ASTextNode()
self.label.isUserInteractionEnabled = false
self.label.maximumNumberOfLines = 1
self.label.displaysAsynchronously = false
self.label.truncationMode = .byTruncatingTail
self.label.isAccessibilityElement = false
self.switchNode = SwitchNode()
self.switchNode.frameColor = theme.switchFrameColor
self.switchNode.contentColor = theme.switchContentColor
self.switchNode.handleColor = theme.switchHandleColor
self.switchNode.isAccessibilityElement = false
self.accessibilityArea = AccessibilityAreaNode()
super.init(theme: theme)
@ -62,19 +69,35 @@ public class ActionSheetSwitchNode: ActionSheetItemNode {
self.addSubnode(self.label)
self.addSubnode(self.switchNode)
self.addSubnode(self.accessibilityArea)
self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
self.switchNode.valueUpdated = { [weak self] value in
self?.item?.action(value)
}
self.accessibilityArea.activate = { [weak self] in
self?.buttonPressed()
return true
}
}
func setItem(_ item: ActionSheetSwitchItem) {
self.item = item
self.label.attributedText = NSAttributedString(string: item.title, font: ActionSheetButtonNode.defaultFont, textColor: self.theme.primaryTextColor)
self.label.isAccessibilityElement = false
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()
}
@ -94,6 +117,8 @@ public class ActionSheetSwitchNode: ActionSheetItemNode {
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.accessibilityArea.frame = CGRect(origin: CGPoint(), size: size)
}
@objc func buttonPressed() {

View File

@ -34,6 +34,8 @@ public class ActionSheetTextNode: ActionSheetItemNode {
private let label: ASTextNode
private let accessibilityArea: AccessibilityAreaNode
override public init(theme: ActionSheetControllerTheme) {
self.theme = theme
@ -42,17 +44,24 @@ public class ActionSheetTextNode: ActionSheetItemNode {
self.label.maximumNumberOfLines = 0
self.label.displaysAsynchronously = false
self.label.truncationMode = .byTruncatingTail
self.label.isAccessibilityElement = false
self.accessibilityArea = AccessibilityAreaNode()
self.accessibilityArea.accessibilityTraits = .staticText
super.init(theme: theme)
self.label.isUserInteractionEnabled = false
self.addSubnode(self.label)
self.addSubnode(self.accessibilityArea)
}
func setItem(_ item: ActionSheetTextItem) {
self.item = item
self.label.attributedText = NSAttributedString(string: item.title, font: ActionSheetTextNode.defaultFont, textColor: self.theme.secondaryTextColor, paragraphAlignment: .center)
self.accessibilityArea.accessibilityLabel = item.title
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))
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 "LegacyComponentsInternal.h"
NSString *const TGMediaAssetsPhotoCellKind = @"TGMediaAssetsPhotoCellKind";
@implementation TGMediaAssetsPhotoCell
@ -7,7 +9,7 @@ NSString *const TGMediaAssetsPhotoCellKind = @"TGMediaAssetsPhotoCellKind";
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self != nil) {
self.accessibilityLabel = @"Photo";
self.accessibilityLabel = TGLocalized(@"Message.Photo");
}
return self;
}

View File

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

View File

@ -394,7 +394,7 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius
[[_presentation view] addSubview:_innerIconWrapperView];
_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.exclusiveTouch = true;
[_stopButton setImage:[self stopButtonImage] forState:UIControlStateNormal];

View File

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

View File

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

View File

@ -187,6 +187,8 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
var editableControlNode: ItemListEditableControlNode?
private let accessibilityArea: AccessibilityAreaNode
private var avatarState: (Account, Peer?)?
private var layoutParams: (CallListCallItem, ListViewItemLayoutParams, Bool, Bool, Bool)?
@ -218,6 +220,8 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
self.infoButtonNode = HighlightableButtonNode()
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)
self.addSubnode(self.backgroundNode)
@ -227,8 +231,17 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
self.addSubnode(self.statusNode)
self.addSubnode(self.dateNode)
self.addSubnode(self.infoButtonNode)
self.addSubnode(self.accessibilityArea)
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?) {
@ -561,6 +574,15 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
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.setRevealOptionsOpened(item.revealed, animated: animated)
}
@ -658,4 +680,10 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
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 checkNode: ASImageNode
private let accessibilityArea: AccessibilityAreaNode
override public init(theme: ActionSheetControllerTheme) {
self.theme = theme
self.button = HighlightTrackingButton()
self.button.isAccessibilityElement = false
self.label = ASTextNode()
self.label.isUserInteractionEnabled = false
self.label.maximumNumberOfLines = 1
self.label.displaysAsynchronously = false
self.label.truncationMode = .byTruncatingTail
self.label.isAccessibilityElement = false
self.iconNode = ASImageNode()
self.iconNode.isUserInteractionEnabled = false
self.iconNode.displayWithoutProcessing = true
self.iconNode.displaysAsynchronously = false
self.iconNode.isAccessibilityElement = false
self.checkNode = ASImageNode()
self.checkNode.isUserInteractionEnabled = false
@ -73,6 +78,9 @@ public class CallRouteActionSheetItemNode: ActionSheetItemNode {
context.addLine(to: CGPoint(x: 1.0, y: 5.81145833))
context.strokePath()
})
self.checkNode.isAccessibilityElement = false
self.accessibilityArea = AccessibilityAreaNode()
super.init(theme: theme)
@ -82,6 +90,7 @@ public class CallRouteActionSheetItemNode: ActionSheetItemNode {
self.addSubnode(self.label)
self.addSubnode(self.iconNode)
self.addSubnode(self.checkNode)
self.addSubnode(self.accessibilityArea)
self.button.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
@ -96,6 +105,11 @@ public class CallRouteActionSheetItemNode: ActionSheetItemNode {
}
self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
self.accessibilityArea.activate = { [weak self] in
self?.buttonPressed()
return true
}
}
func setItem(_ item: CallRouteActionSheetItem) {
@ -109,6 +123,13 @@ public class CallRouteActionSheetItemNode: ActionSheetItemNode {
}
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()
}

View File

@ -41,7 +41,7 @@ private enum ChangePhoneNumberCodeTag: ItemListItemTag {
}
private enum ChangePhoneNumberCodeEntry: ItemListNodeEntry {
case codeEntry(PresentationTheme, String, String)
case codeEntry(PresentationTheme, PresentationStrings, String, String)
case codeInfo(PresentationTheme, String)
var section: ItemListSectionId {
@ -59,8 +59,8 @@ private enum ChangePhoneNumberCodeEntry: ItemListNodeEntry {
static func ==(lhs: ChangePhoneNumberCodeEntry, rhs: ChangePhoneNumberCodeEntry) -> Bool {
switch lhs {
case let .codeEntry(lhsTheme, lhsTitle, lhsText):
if case let .codeEntry(rhsTheme, rhsTitle, rhsText) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsText == rhsText {
case let .codeEntry(lhsTheme, lhsStrings, lhsTitle, lhsText):
if case let .codeEntry(rhsTheme, rhsStrings, rhsTitle, rhsText) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsTitle == rhsTitle, lhsText == rhsText {
return true
} else {
return false
@ -80,8 +80,8 @@ private enum ChangePhoneNumberCodeEntry: ItemListNodeEntry {
func item(_ arguments: ChangePhoneNumberCodeControllerArguments) -> ListViewItem {
switch self {
case let .codeEntry(theme, 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
case let .codeEntry(theme, strings, title, text):
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)
}, action: {
arguments.next()
@ -132,7 +132,7 @@ private struct ChangePhoneNumberCodeControllerState: Equatable {
private func changePhoneNumberCodeControllerEntries(presentationData: PresentationData, state: ChangePhoneNumberCodeControllerState, codeData: ChangeAccountPhoneNumberData, timeout: Int32?, strings: PresentationStrings) -> [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
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

View File

@ -148,7 +148,7 @@ private enum ChannelAdminEntryStableId: Hashable {
private enum ChannelAdminEntry: ItemListNodeEntry {
case info(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, TelegramUserPresence?)
case rankTitle(PresentationTheme, String, Int32?, Int32)
case rank(PresentationTheme, String, String, Bool)
case rank(PresentationTheme, PresentationStrings, String, String, Bool)
case rankInfo(PresentationTheme, String)
case rightsTitle(PresentationTheme, String)
case rightItem(PresentationTheme, Int, String, TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags, Bool, Bool)
@ -224,8 +224,8 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
} else {
return false
}
case let .rank(lhsTheme, lhsPlaceholder, lhsValue, lhsEnabled):
if case let .rank(rhsTheme, rhsPlaceholder, rhsValue, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsPlaceholder == rhsPlaceholder, lhsValue == rhsValue, lhsEnabled == rhsEnabled {
case let .rank(lhsTheme, lhsStrings, lhsPlaceholder, lhsValue, lhsEnabled):
if case let .rank(rhsTheme, rhsStrings, rhsPlaceholder, rhsValue, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsPlaceholder == rhsPlaceholder, lhsValue == rhsValue, lhsEnabled == rhsEnabled {
return true
} else {
return false
@ -367,8 +367,8 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
accessoryText = ItemListSectionHeaderAccessoryText(value: "\(limit - count)", color: count > limit ? .destructive : .generic)
}
return ItemListSectionHeaderItem(theme: theme, text: text, accessoryText: accessoryText, sectionId: self.section)
case let .rank(theme, 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
case let .rank(theme, strings, placeholder, text, enabled):
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)
}, shouldUpdateText: { text in
if text.containsEmoji {
@ -663,7 +663,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
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(.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))
}
@ -689,7 +689,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
if isCreator {
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 {
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(.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 {
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 publicLinkAvailability(PresentationTheme, String, Bool)
case privateLink(PresentationTheme, String, String?)
case editablePublicLink(PresentationTheme, String, String)
case editablePublicLink(PresentationTheme, PresentationStrings, String, String)
case privateLinkInfo(PresentationTheme, String)
case privateLinkCopy(PresentationTheme, String)
case privateLinkRevoke(PresentationTheme, String)
@ -169,8 +169,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
} else {
return false
}
case let .editablePublicLink(lhsTheme, lhsPlaceholder, lhsCurrentText):
if case let .editablePublicLink(rhsTheme, rhsPlaceholder, rhsCurrentText) = rhs, lhsTheme === rhsTheme, lhsPlaceholder == rhsPlaceholder, lhsCurrentText == rhsCurrentText {
case let .editablePublicLink(lhsTheme, lhsStrings, lhsPlaceholder, lhsCurrentText):
if case let .editablePublicLink(rhsTheme, rhsStrings, rhsPlaceholder, rhsCurrentText) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsPlaceholder == rhsPlaceholder, lhsCurrentText == rhsCurrentText {
return true
} else {
return false
@ -280,8 +280,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
arguments.displayPrivateLinkMenu(value)
}
}, tag: ChannelVisibilityEntryTag.privateLink)
case let .editablePublicLink(theme, 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
case let .editablePublicLink(theme, strings, placeholder, currentText):
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)
}, updatedFocus: { focus in
if focus {
@ -527,7 +527,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp, true))
}
} 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 {
let text: String
switch status {
@ -669,7 +669,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp, true))
}
} else {
entries.append(.editablePublicLink(presentationData.theme, "", currentAddressName))
entries.append(.editablePublicLink(presentationData.theme, presentationData.strings, "", currentAddressName))
if let status = state.addressNameValidationStatus {
let text: String
switch status {

View File

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

View File

@ -1319,7 +1319,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
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 {
self.textInputPanelNode?.updateKeepSendButtonEnabled(keepSendButtonEnabled: keepSendButtonEnabled, extendedSearchLayout: extendedSearchLayout, animated: transition.isAnimated)
}

View File

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

View File

@ -54,7 +54,7 @@ func rightNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Ch
} else if let peer = presentationInterfaceState.renderedPeer?.peer {
if presentationInterfaceState.accountPeerId == peer.id {
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)
}
}

View File

@ -112,7 +112,7 @@ public class ChatListController: TelegramController, UIViewControllerPreviewingD
self.presentationData = (context.sharedContext.currentPresentationData.with { $0 })
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)
@ -147,7 +147,7 @@ public class ChatListController: TelegramController, UIViewControllerPreviewingD
self.navigationItem.leftBarButtonItem = leftBarButtonItem
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
let backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.DialogList_Title, style: .plain, target: nil, action: nil)
backBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Back
@ -236,7 +236,7 @@ public class ChatListController: TelegramController, UIViewControllerPreviewingD
if case .root = strongSelf.groupId {
isRoot = true
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
}
@ -393,7 +393,7 @@ public class ChatListController: TelegramController, UIViewControllerPreviewingD
if case .root = self.groupId {
self.navigationItem.leftBarButtonItem = editItem
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
} else {
self.navigationItem.rightBarButtonItem = editItem

View File

@ -262,6 +262,16 @@ private func leftRevealOptions(strings: PresentationStrings, theme: Presentation
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 avatarFont = UIFont(name: ".SFCompactRounded-Semibold", size: 26.0)!
@ -1078,6 +1088,14 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
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
if let strongSelf = self {
strongSelf.layoutParams = (item, first, last, firstWithHeader, nextIsPinned, params, countersSize)
@ -1449,6 +1467,14 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
strongSelf.view.accessibilityLabel = strongSelf.accessibilityLabel
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.strings = strings
self.titleNode = ImmediateTextNode()
self.titleNode.displaysAsynchronously = false
@ -96,7 +99,7 @@ final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitl
self.proxyButton = HighlightTrackingButton()
self.proxyButton.isHidden = true
self.proxyButton.isAccessibilityElement = true
self.proxyButton.accessibilityLabel = "Proxy Settings"
self.proxyButton.accessibilityLabel = self.strings.VoiceOver_Navigation_ProxySettings
self.proxyButton.accessibilityTraits = .button
super.init(frame: CGRect())
@ -213,7 +216,7 @@ final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitl
}
func makeTransitionMirrorNode() -> ASDisplayNode {
let view = ChatListTitleView(theme: self.theme)
let view = ChatListTitleView(theme: self.theme, strings: self.strings)
view.title = self.title
return ASDisplayNode(viewBlock: {

View File

@ -20,6 +20,8 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
var longTapRecognizer: UILongPressGestureRecognizer?
private let accessibilityArea: AccessibilityAreaNode
override init() {
self.backgroundNode = ASImageNode()
self.backgroundNode.displayWithoutProcessing = true
@ -28,9 +30,18 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
self.backgroundNode.alpha = 1.0
self.backgroundNode.isUserInteractionEnabled = false
self.accessibilityArea = AccessibilityAreaNode()
self.accessibilityArea.accessibilityTraits = .button
super.init()
self.addSubnode(self.backgroundNode)
self.addSubnode(self.accessibilityArea)
self.accessibilityArea.activate = { [weak self] in
self?.buttonPressed()
return true
}
}
override func didLoad() {
@ -39,6 +50,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
let buttonView = HighlightTrackingButton(frame: self.bounds)
buttonView.addTarget(self, action: #selector(self.buttonPressed), for: [.touchUpInside])
self.buttonView = buttonView
buttonView.isAccessibilityElement = false
self.view.addSubview(buttonView)
buttonView.highligthedChanged = { [weak self] highlighted in
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.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
})
})

View File

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

View File

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

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.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 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.deleteButton = HighlightableButtonNode()
self.deleteButton.isEnabled = false
self.deleteButton.isAccessibilityElement = true
self.deleteButton.accessibilityLabel = "Delete"
self.deleteButton.accessibilityLabel = strings.VoiceOver_MessageContextDelete
self.reportButton = HighlightableButtonNode()
self.reportButton.isEnabled = false
self.reportButton.isAccessibilityElement = true
self.reportButton.accessibilityLabel = "Report"
self.reportButton.accessibilityLabel = strings.VoiceOver_MessageContextReport
self.forwardButton = HighlightableButtonNode()
self.forwardButton.isAccessibilityElement = true
self.forwardButton.accessibilityLabel = "Forward"
self.forwardButton.accessibilityLabel = strings.VoiceOver_MessageContextForward
self.shareButton = HighlightableButtonNode()
self.shareButton.isEnabled = false
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.panelControlDisabledColor), for: [.disabled])

View File

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

View File

@ -5,6 +5,8 @@ import Display
import TelegramPresentationData
final class ChatTextInputActionButtonsNode: ASDisplayNode {
private let strings: PresentationStrings
let micButton: ChatTextInputMediaRecordingButton
let sendButton: HighlightTrackingButton
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.sendButton = HighlightTrackingButton()
self.sendButton.adjustsImageWhenHighlighted = false
@ -126,15 +130,15 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode {
self.accessibilityTraits = .button
switch self.micButton.mode {
case .audio:
self.accessibilityLabel = "Voice Message"
self.accessibilityHint = "Double tap and hold to record voice message. Slide up to pin recording, slide left to cancel. Double tap to switch to video."
self.accessibilityLabel = self.strings.VoiceOver_Chat_RecordModeVoiceMessage
self.accessibilityHint = self.strings.VoiceOver_Chat_RecordModeVoiceMessageInfo
case .video:
self.accessibilityLabel = "Video Message"
self.accessibilityHint = "Double tap and hold to record voice message. Slide up to pin recording, slide left to cancel. Double tap to switch to audio."
self.accessibilityLabel = self.strings.VoiceOver_Chat_RecordModeVideoMessage
self.accessibilityHint = self.strings.VoiceOver_Chat_RecordModeVideoMessageInfo
}
} else {
self.accessibilityTraits = .button
self.accessibilityLabel = "Send"
self.accessibilityLabel = self.strings.MediaPicker_Send
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 {
self.loadTextInputNode()
}
if let textInputNode = self.textInputNode {
if let textInputNode = self.textInputNode, let currentState = self.presentationInterfaceState {
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 accentTextColor: UIColor = .blue
var baseFontSize: CGFloat = 17.0
@ -335,14 +372,14 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
self.textPlaceholderNode.maximumNumberOfLines = 1
self.textPlaceholderNode.isUserInteractionEnabled = false
self.attachmentButton = HighlightableButtonNode()
self.attachmentButton.accessibilityLabel = "Send media"
self.attachmentButton.accessibilityLabel = presentationInterfaceState.strings.VoiceOver_AttachMedia
self.attachmentButton.isAccessibilityElement = true
self.attachmentButtonDisabledNode = HighlightableButtonNode()
self.searchLayoutClearButton = HighlightableButton()
self.searchLayoutProgressView = UIImageView(image: searchLayoutProgressImage)
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()
@ -551,7 +588,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
let textFieldHeight: CGFloat
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)

View File

@ -41,7 +41,7 @@ private enum ConfirmPhoneNumberCodeTag: ItemListItemTag {
}
private enum ConfirmPhoneNumberCodeEntry: ItemListNodeEntry {
case codeEntry(PresentationTheme, String, String)
case codeEntry(PresentationTheme, PresentationStrings, String, String)
case codeInfo(PresentationTheme, PresentationStrings, String, String)
var section: ItemListSectionId {
@ -59,8 +59,8 @@ private enum ConfirmPhoneNumberCodeEntry: ItemListNodeEntry {
static func ==(lhs: ConfirmPhoneNumberCodeEntry, rhs: ConfirmPhoneNumberCodeEntry) -> Bool {
switch lhs {
case let .codeEntry(lhsTheme, lhsTitle, lhsText):
if case let .codeEntry(rhsTheme, rhsTitle, rhsText) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsText == rhsText {
case let .codeEntry(lhsTheme, lhsStrings, lhsTitle, lhsText):
if case let .codeEntry(rhsTheme, rhsStrings, rhsTitle, rhsText) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsTitle == rhsTitle, lhsText == rhsText {
return true
} else {
return false
@ -80,8 +80,8 @@ private enum ConfirmPhoneNumberCodeEntry: ItemListNodeEntry {
func item(_ arguments: ConfirmPhoneNumberCodeControllerArguments) -> ListViewItem {
switch self {
case let .codeEntry(theme, 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
case let .codeEntry(theme, strings, title, text):
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)
}, action: {
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] {
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 = ""
if let nextType = codeData.nextType {
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 {
case passwordHeader(PresentationTheme, String)
case password(PresentationTheme, String, String)
case passwordConfirmation(PresentationTheme, String, String)
case password(PresentationTheme, PresentationStrings, String, String)
case passwordConfirmation(PresentationTheme, PresentationStrings, String, String)
case passwordInfo(PresentationTheme, String)
case hintHeader(PresentationTheme, String)
case hint(PresentationTheme, String, String, Bool)
case hint(PresentationTheme, PresentationStrings, String, String, Bool)
case hintInfo(PresentationTheme, String)
case emailHeader(PresentationTheme, String)
case email(PresentationTheme, String, String)
case email(PresentationTheme, PresentationStrings, String, String)
case emailInfo(PresentationTheme, String)
case emailConfirmation(PresentationTheme, String)
@ -117,14 +117,14 @@ private enum CreatePasswordEntry: ItemListNodeEntry, Equatable {
switch self {
case let .passwordHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .password(theme, 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
case let .password(theme, strings, text, value):
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)
}, action: {
arguments.selectNextInputItem(CreatePasswordEntryTag.password)
})
case let .passwordConfirmation(theme, 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
case let .passwordConfirmation(theme, strings, text, value):
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)
}, action: {
arguments.selectNextInputItem(CreatePasswordEntryTag.passwordConfirmation)
@ -133,8 +133,8 @@ private enum CreatePasswordEntry: ItemListNodeEntry, Equatable {
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
case let .hintHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .hint(theme, 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
case let .hint(theme, strings, text, value, last):
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)
}, action: {
if last {
@ -147,8 +147,8 @@ private enum CreatePasswordEntry: ItemListNodeEntry, Equatable {
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
case let .emailHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .email(theme, 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
case let .email(theme, strings, text, value):
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)
}, action: {
arguments.save()
@ -184,8 +184,8 @@ private func createPasswordControllerEntries(presentationData: PresentationData,
switch state.state {
case let .setup(currentPassword):
entries.append(.passwordHeader(presentationData.theme, presentationData.strings.FastTwoStepSetup_PasswordSection))
entries.append(.password(presentationData.theme, presentationData.strings.FastTwoStepSetup_PasswordPlaceholder, state.passwordText))
entries.append(.passwordConfirmation(presentationData.theme, presentationData.strings.FastTwoStepSetup_PasswordConfirmationPlaceholder, state.passwordConfirmationText))
entries.append(.password(presentationData.theme, presentationData.strings, presentationData.strings.FastTwoStepSetup_PasswordPlaceholder, state.passwordText))
entries.append(.passwordConfirmation(presentationData.theme, presentationData.strings, presentationData.strings.FastTwoStepSetup_PasswordConfirmationPlaceholder, state.passwordConfirmationText))
if case .paymentInfo = context {
entries.append(.passwordInfo(presentationData.theme, presentationData.strings.FastTwoStepSetup_PasswordHelp))
@ -194,12 +194,12 @@ private func createPasswordControllerEntries(presentationData: PresentationData,
let showEmail = currentPassword == nil
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))
if showEmail {
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))
}
case let .pendingVerification(emailPattern):

View File

@ -42,20 +42,28 @@ private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode {
private let avatarNode: AvatarNode
private let textNode: ImmediateTextNode
private let accessibilityArea: AccessibilityAreaNode
init(theme: ActionSheetControllerTheme, strings: PresentationStrings, context: AccountContextImpl, peer: Peer, chatPeer: Peer, action: DeleteChatPeerAction) {
self.theme = theme
self.strings = strings
self.avatarNode = AvatarNode(font: avatarFont)
self.avatarNode.isAccessibilityElement = false
self.textNode = ImmediateTextNode()
self.textNode.displaysAsynchronously = false
self.textNode.maximumNumberOfLines = 0
self.textNode.textAlignment = .center
self.textNode.isAccessibilityElement = false
self.accessibilityArea = AccessibilityAreaNode()
super.init(theme: theme)
self.addSubnode(self.avatarNode)
self.addSubnode(self.textNode)
self.addSubnode(self.accessibilityArea)
if chatPeer.id == context.account.peerId {
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.accessibilityArea.accessibilityLabel = attributedText.string
self.accessibilityArea.accessibilityTraits = .staticText
}
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.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() {

View File

@ -67,7 +67,7 @@ final class EditAccessoryPanelNode: AccessoryPanelNode {
self.nameDisplayOrder = nameDisplayOrder
self.closeButton = ASButtonNode()
self.closeButton.accessibilityLabel = "Discard"
self.closeButton.accessibilityLabel = strings.VoiceOver_DiscardPreparedContent
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.displaysAsynchronously = false

View File

@ -83,7 +83,7 @@ final class ForwardAccessoryPanelNode: AccessoryPanelNode {
self.theme = theme
self.closeButton = ASButtonNode()
self.closeButton.accessibilityLabel = "Discard"
self.closeButton.accessibilityLabel = strings.VoiceOver_DiscardPreparedContent
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.displaysAsynchronously = false

View File

@ -62,7 +62,7 @@ private enum GroupStickerPackEntryId: Hashable {
}
private enum GroupStickerPackEntry: ItemListNodeEntry {
case search(PresentationTheme, String, String, String)
case search(PresentationTheme, PresentationStrings, String, String, String)
case currentPack(Int32, PresentationTheme, PresentationStrings, GroupStickerPackCurrentItemContent)
case searchInfo(PresentationTheme, String)
case packsTitle(PresentationTheme, String)
@ -94,8 +94,8 @@ private enum GroupStickerPackEntry: ItemListNodeEntry {
static func ==(lhs: GroupStickerPackEntry, rhs: GroupStickerPackEntry) -> Bool {
switch lhs {
case let .search(lhsTheme, lhsPrefix, lhsPlaceholder, lhsValue):
if case let .search(rhsTheme, rhsPrefix, rhsPlaceholder, rhsValue) = rhs, lhsTheme === rhsTheme, lhsPrefix == rhsPrefix, lhsPlaceholder == rhsPlaceholder, lhsValue == rhsValue {
case let .search(lhsTheme, lhsStrings, lhsPrefix, lhsPlaceholder, lhsValue):
if case let .search(rhsTheme, rhsStrings, rhsPrefix, rhsPlaceholder, rhsValue) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsPrefix == rhsPrefix, lhsPlaceholder == rhsPlaceholder, lhsValue == rhsValue {
return true
} else {
return false
@ -205,8 +205,8 @@ private enum GroupStickerPackEntry: ItemListNodeEntry {
func item(_ arguments: GroupStickerPackSetupControllerArguments) -> ListViewItem {
switch self {
case let .search(theme, 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
case let .search(theme, strings, prefix, placeholder, value):
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)
}, processPaste: { text in
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] = []
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 {
case .none:
break

View File

@ -54,6 +54,8 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
var currentExpandedDetails: [Int : Bool]?
var currentDetailsItems: [InstantPageDetailsItem] = []
var currentAccessibilityAreas: [AccessibilityAreaNode] = []
private var previousContentOffset: CGPoint?
private var isDeceleratingBecauseOfDragging = false
@ -428,12 +430,22 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.currentExpandedDetails = expandedDetails
}
let accessibilityAreas = instantPageAccessibilityAreasFromLayout(currentLayout, boundingWidth: containerLayout.size.width)
self.currentLayout = currentLayout
self.currentLayoutTiles = currentLayoutTiles
self.currentLayoutItemsWithNodes = currentLayoutItemsWithNodes
self.currentDetailsItems = currentDetailsItems
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.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 UIKit
import Display
final class InstantPageTile {
let frame: CGRect
@ -79,3 +80,17 @@ func instantPageTilesFromLayout(_ layout: InstantPageLayout, boundingWidth: CGFl
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
var callNodes: [(TextNode, TextNode)]
private let accessibilityArea: AccessibilityAreaNode
private var item: ItemListCallListItem?
override var canBeSelected: Bool {
@ -127,12 +129,16 @@ class ItemListCallListItemNode: ListViewItemNode {
self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false
self.titleNode.isAccessibilityElement = false
self.callNodes = []
self.accessibilityArea = AccessibilityAreaNode()
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.titleNode)
self.addSubnode(self.accessibilityArea)
}
func asyncLayout() -> (_ item: ItemListCallListItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
@ -295,6 +301,8 @@ class ItemListCallListItemNode: ListViewItemNode {
yOffset += layout.0.size.height + 12.0
index += 1
}
strongSelf.accessibilityArea.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
}
})
}

View File

@ -16,6 +16,7 @@ enum ItemListSingleLineInputItemType: Equatable {
class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
let theme: PresentationTheme
let strings: PresentationStrings
let title: NSAttributedString
let text: String
let placeholder: String
@ -32,8 +33,9 @@ class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
let updatedFocus: ((Bool) -> Void)?
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.strings = strings
self.title = title
self.text = text
self.placeholder = placeholder
@ -123,7 +125,6 @@ class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDelegate, It
self.clearIconNode.displaysAsynchronously = false
self.clearButtonNode = HighlightableButtonNode()
self.clearButtonNode.accessibilityLabel = "Clear Text"
super.init(layerBacked: false, dynamicBounce: false)
@ -321,6 +322,8 @@ class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDelegate, It
strongSelf.textNode.isUserInteractionEnabled = item.enabled
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 {
case .x1:
self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateInactiveIcon(self.theme), for: [])
self.rateButton.accessibilityLabel = "Playback rate"
self.rateButton.accessibilityValue = "Normal"
self.rateButton.accessibilityHint = "Double tap to change"
self.rateButton.accessibilityLabel = self.strings.VoiceOver_Media_PlaybackRate
self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRateNormal
self.rateButton.accessibilityHint = self.strings.VoiceOver_Media_PlaybackRateChange
case .x2:
self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateActiveIcon(self.theme), for: [])
self.rateButton.accessibilityLabel = "Playback rate"
self.rateButton.accessibilityValue = "Fast"
self.rateButton.accessibilityHint = "Double tap to change"
self.rateButton.accessibilityLabel = self.strings.VoiceOver_Media_PlaybackRate
self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRateFast
self.rateButton.accessibilityHint = self.strings.VoiceOver_Media_PlaybackRateChange
}
}
}
@ -224,7 +224,7 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollViewDeleg
self.rightMaskNode.image = maskImage
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.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)
@ -336,7 +336,7 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollViewDeleg
}
strongSelf.actionPlayNode.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 {
case .entry:
self.modeButtonNode.isHidden = true
self.modeButtonNode.isAccessibilityElement = false
text = self.presentationData.strings.EnterPasscode_EnterPasscode
case let .setup(change, _):
if change {
@ -219,6 +220,9 @@ final class PasscodeSetupControllerNode: ASDisplayNode {
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.modeButtonNode.isHidden = false
self.modeButtonNode.isAccessibilityElement = true
UIAccessibility.post(notification: UIAccessibility.Notification.announcement, argument: self.presentationData.strings.PasscodeSettings_DoNotMatch)
if let validLayout = self.validLayout {
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.subtitleNode.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 {
self.containerLayoutUpdated(validLayout.0, navigationBarHeight: validLayout.1, transition: .immediate)
@ -251,6 +258,8 @@ final class PasscodeSetupControllerNode: ASDisplayNode {
func activateInput() {
self.inputFieldNode.activateInput()
UIAccessibility.post(notification: UIAccessibility.Notification.announcement, argument: self.titleNode.attributedText?.string)
}
func animateError() {

View File

@ -268,7 +268,7 @@ class PeerMediaCollectionControllerNode: ASDisplayNode {
self.addSubnode(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.backgroundColor = self.presentationData.theme.chat.inputPanel.panelBackgroundColor
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.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.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.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 connectionHeader(PresentationTheme, String)
case connectionServer(PresentationTheme, String, String)
case connectionPort(PresentationTheme, String, String)
case connectionServer(PresentationTheme, PresentationStrings, String, String)
case connectionPort(PresentationTheme, PresentationStrings, String, String)
case credentialsHeader(PresentationTheme, String)
case credentialsUsername(PresentationTheme, String, String)
case credentialsPassword(PresentationTheme, String, String)
case credentialsSecret(PresentationTheme, String, String)
case credentialsUsername(PresentationTheme, PresentationStrings, String, String)
case credentialsPassword(PresentationTheme, PresentationStrings, String, String)
case credentialsSecret(PresentationTheme, PresentationStrings, String, String)
case share(PresentationTheme, String, Bool)
@ -138,16 +138,16 @@ private enum ProxySettingsEntry: ItemListNodeEntry {
})
case let .connectionHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .connectionServer(theme, placeholder, text):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(), text: text, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: false), sectionId: self.section, textUpdated: { value in
case let .connectionServer(theme, strings, placeholder, text):
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
var state = current
state.host = value
return state
}
}, action: {})
case let .connectionPort(theme, placeholder, text):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(), text: text, placeholder: placeholder, type: .number, sectionId: self.section, textUpdated: { value in
case let .connectionPort(theme, strings, placeholder, text):
return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(), text: text, placeholder: placeholder, type: .number, sectionId: self.section, textUpdated: { value in
arguments.updateState { current in
var state = current
state.port = value
@ -156,24 +156,24 @@ private enum ProxySettingsEntry: ItemListNodeEntry {
}, action: {})
case let .credentialsHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .credentialsUsername(theme, placeholder, text):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(), text: text, placeholder: placeholder, sectionId: self.section, textUpdated: { value in
case let .credentialsUsername(theme, strings, placeholder, text):
return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(), text: text, placeholder: placeholder, sectionId: self.section, textUpdated: { value in
arguments.updateState { current in
var state = current
state.username = value
return state
}
}, action: {})
case let .credentialsPassword(theme, placeholder, text):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(), text: text, placeholder: placeholder, type: .password, sectionId: self.section, textUpdated: { value in
case let .credentialsPassword(theme, strings, placeholder, text):
return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(), text: text, placeholder: placeholder, type: .password, sectionId: self.section, textUpdated: { value in
arguments.updateState { current in
var state = current
state.password = value
return state
}
}, action: {})
case let .credentialsSecret(theme, placeholder, text):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(), text: text, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: false), sectionId: self.section, textUpdated: { value in
case let .credentialsSecret(theme, strings, placeholder, text):
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
var state = current
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(.connectionHeader(presentationData.theme, presentationData.strings.SocksProxySetup_Connection.uppercased()))
entries.append(.connectionServer(presentationData.theme, presentationData.strings.SocksProxySetup_Hostname, state.host))
entries.append(.connectionPort(presentationData.theme, presentationData.strings.SocksProxySetup_Port, state.port))
entries.append(.connectionServer(presentationData.theme, presentationData.strings, presentationData.strings.SocksProxySetup_Hostname, state.host))
entries.append(.connectionPort(presentationData.theme, presentationData.strings, presentationData.strings.SocksProxySetup_Port, state.port))
switch state.mode {
case .socks5:
entries.append(.credentialsHeader(presentationData.theme, presentationData.strings.SocksProxySetup_Credentials))
entries.append(.credentialsUsername(presentationData.theme, presentationData.strings.SocksProxySetup_Username, state.username))
entries.append(.credentialsPassword(presentationData.theme, presentationData.strings.SocksProxySetup_Password, state.password))
entries.append(.credentialsUsername(presentationData.theme, presentationData.strings, presentationData.strings.SocksProxySetup_Username, state.username))
entries.append(.credentialsPassword(presentationData.theme, presentationData.strings, presentationData.strings.SocksProxySetup_Password, state.password))
case .mtp:
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))

View File

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

View File

@ -30,7 +30,7 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode {
self.theme = theme
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.setImage(PresentationResourcesChat.chatInputPanelCloseIconImage(theme), for: [])
self.closeButton.displaysAsynchronously = false

View File

@ -35,7 +35,7 @@ private enum ResetPasswordEntryTag: ItemListItemTag {
}
private enum ResetPasswordEntry: ItemListNodeEntry, Equatable {
case code(PresentationTheme, String, String)
case code(PresentationTheme, PresentationStrings, String, String)
case codeInfo(PresentationTheme, String)
case helpInfo(PresentationTheme, String)
@ -65,8 +65,8 @@ private enum ResetPasswordEntry: ItemListNodeEntry, Equatable {
func item(_ arguments: ResetPasswordControllerArguments) -> ListViewItem {
switch self {
case let .code(theme, 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
case let .code(theme, strings, text, value):
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)
}, action: {
})
@ -90,7 +90,7 @@ private struct ResetPasswordControllerState: Equatable {
private func resetPasswordControllerEntries(presentationData: PresentationData, state: ResetPasswordControllerState, pattern: String) -> [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))
let stringData = presentationData.strings.TwoStepAuth_RecoveryEmailUnavailable(pattern)

View File

@ -42,10 +42,10 @@ private enum TwoStepVerificationPasswordEntryTag: ItemListItemTag {
private enum TwoStepVerificationPasswordEntryEntry: ItemListNodeEntry {
case passwordEntryTitle(PresentationTheme, String)
case passwordEntry(PresentationTheme, String)
case passwordEntry(PresentationTheme, PresentationStrings, String)
case hintTitle(PresentationTheme, String)
case hintEntry(PresentationTheme, String)
case hintEntry(PresentationTheme, PresentationStrings, String)
case emailEntry(PresentationTheme, PresentationStrings, String)
case emailInfo(PresentationTheme, String)
@ -79,8 +79,8 @@ private enum TwoStepVerificationPasswordEntryEntry: ItemListNodeEntry {
} else {
return false
}
case let .passwordEntry(lhsTheme, lhsText):
if case let .passwordEntry(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
case let .passwordEntry(lhsTheme, lhsStrings, lhsText):
if case let .passwordEntry(rhsTheme, rhsStrings, rhsText) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsText == rhsText {
return true
} else {
return false
@ -91,8 +91,8 @@ private enum TwoStepVerificationPasswordEntryEntry: ItemListNodeEntry {
} else {
return false
}
case let .hintEntry(lhsTheme, lhsText):
if case let .hintEntry(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
case let .hintEntry(lhsTheme, lhsStrings, lhsText):
if case let .hintEntry(rhsTheme, rhsStrings, rhsText) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsText == rhsText {
return true
} else {
return false
@ -120,22 +120,22 @@ private enum TwoStepVerificationPasswordEntryEntry: ItemListNodeEntry {
switch self {
case let .passwordEntryTitle(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .passwordEntry(theme, 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
case let .passwordEntry(theme, strings, text):
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)
}, action: {
arguments.next()
})
case let .hintTitle(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .hintEntry(theme, 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
case let .hintEntry(theme, strings, text):
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)
}, action: {
arguments.next()
})
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)
}, action: {
arguments.next()
@ -230,13 +230,13 @@ private func twoStepVerificationPasswordEntryControllerEntries(presentationData:
switch state.stage {
case let .entry(text):
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):
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):
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):
entries.append(.emailEntry(presentationData.theme, presentationData.strings, text))
entries.append(.emailInfo(presentationData.theme, presentationData.strings.TwoStepAuth_EmailHelp))

View File

@ -44,7 +44,7 @@ private enum TwoStepVerificationResetTag: ItemListItemTag {
}
private enum TwoStepVerificationResetEntry: ItemListNodeEntry {
case codeEntry(PresentationTheme, String, String)
case codeEntry(PresentationTheme, PresentationStrings, String, String)
case codeInfo(PresentationTheme, String)
var section: ItemListSectionId {
@ -62,8 +62,8 @@ private enum TwoStepVerificationResetEntry: ItemListNodeEntry {
static func ==(lhs: TwoStepVerificationResetEntry, rhs: TwoStepVerificationResetEntry) -> Bool {
switch lhs {
case let .codeEntry(lhsTheme, lhsPlaceholder, lhsText):
if case let .codeEntry(rhsTheme, rhsPlaceholder, rhsText) = rhs, lhsTheme === rhsTheme, lhsPlaceholder == rhsPlaceholder, lhsText == rhsText {
case let .codeEntry(lhsTheme, lhsStrings, lhsPlaceholder, lhsText):
if case let .codeEntry(rhsTheme, rhsStrings, rhsPlaceholder, rhsText) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsPlaceholder == rhsPlaceholder, lhsText == rhsText {
return true
} else {
return false
@ -83,8 +83,8 @@ private enum TwoStepVerificationResetEntry: ItemListNodeEntry {
func item(_ arguments: TwoStepVerificationResetControllerArguments) -> ListViewItem {
switch self {
case let .codeEntry(theme, 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
case let .codeEntry(theme, strings, placeholder, text):
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)
}, action: {
arguments.next()
@ -127,7 +127,7 @@ private struct TwoStepVerificationResetControllerState: Equatable {
private func twoStepVerificationResetControllerEntries(presentationData: PresentationData, state: TwoStepVerificationResetControllerState, emailPattern: String) -> [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)]()"))
return entries

View File

@ -57,7 +57,7 @@ private enum TwoStepVerificationUnlockSettingsEntryTag: ItemListItemTag {
}
private enum TwoStepVerificationUnlockSettingsEntry: ItemListNodeEntry {
case passwordEntry(PresentationTheme, String, String)
case passwordEntry(PresentationTheme, PresentationStrings, String, String)
case passwordEntryInfo(PresentationTheme, String)
case passwordSetup(PresentationTheme, String)
@ -69,7 +69,7 @@ private enum TwoStepVerificationUnlockSettingsEntry: ItemListNodeEntry {
case passwordInfo(PresentationTheme, String)
case pendingEmailConfirmInfo(PresentationTheme, String)
case pendingEmailConfirmCode(PresentationTheme, String, String)
case pendingEmailConfirmCode(PresentationTheme, PresentationStrings, String, String)
case pendingEmailInfo(PresentationTheme, String)
case pendingEmailOpenConfirm(PresentationTheme, String)
@ -117,8 +117,8 @@ private enum TwoStepVerificationUnlockSettingsEntry: ItemListNodeEntry {
func item(_ arguments: TwoStepVerificationUnlockSettingsControllerArguments) -> ListViewItem {
switch self {
case let .passwordEntry(theme, 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
case let .passwordEntry(theme, strings, text, value):
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)
}, action: {
arguments.checkPassword()
@ -152,8 +152,8 @@ private enum TwoStepVerificationUnlockSettingsEntry: ItemListNodeEntry {
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
case let .pendingEmailConfirmInfo(theme, text):
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
case let .pendingEmailConfirmCode(theme, title, text):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(string: ""), text: text, placeholder: title, type: .number, sectionId: self.section, textUpdated: { value in
case let .pendingEmailConfirmCode(theme, strings, title, text):
return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(string: ""), text: text, placeholder: title, type: .number, sectionId: self.section, textUpdated: { value in
arguments.updateEmailCode(value)
}, action: {})
case let .pendingEmailInfo(theme, text):
@ -187,7 +187,7 @@ private func twoStepVerificationUnlockSettingsControllerEntries(presentationData
case let .notSet(pendingEmail):
if let pendingEmail = pendingEmail {
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_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))
}
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 {
entries.append(.passwordEntryInfo(presentationData.theme, presentationData.strings.TwoStepAuth_EnterPasswordHelp + "\n\n[" + presentationData.strings.TwoStepAuth_EnterPasswordForgot + "](forgot)"))
} else {

View File

@ -38,7 +38,7 @@ public enum UsernameEntryTag: ItemListItemTag {
private enum UsernameSetupEntry: ItemListNodeEntry {
case editablePublicLink(PresentationTheme, String, String?, String)
case editablePublicLink(PresentationTheme, PresentationStrings, String, String?, String)
case publicLinkStatus(PresentationTheme, String, AddressNameValidationStatus, String)
case publicLinkInfo(PresentationTheme, String)
@ -62,8 +62,8 @@ private enum UsernameSetupEntry: ItemListNodeEntry {
static func ==(lhs: UsernameSetupEntry, rhs: UsernameSetupEntry) -> Bool {
switch lhs {
case let .editablePublicLink(lhsTheme, lhsPrefix, lhsCurrentText, lhsText):
if case let .editablePublicLink(rhsTheme, rhsPrefix, rhsCurrentText, rhsText) = rhs, lhsTheme === rhsTheme, lhsPrefix == rhsPrefix, lhsCurrentText == rhsCurrentText, lhsText == rhsText {
case let .editablePublicLink(lhsTheme, lhsStrings, lhsPrefix, lhsCurrentText, lhsText):
if case let .editablePublicLink(rhsTheme, rhsStrings, rhsPrefix, rhsCurrentText, rhsText) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsPrefix == rhsPrefix, lhsCurrentText == rhsCurrentText, lhsText == rhsText {
return true
} else {
return false
@ -89,8 +89,8 @@ private enum UsernameSetupEntry: ItemListNodeEntry {
func item(_ arguments: UsernameSetupControllerArguments) -> ListViewItem {
switch self {
case let .editablePublicLink(theme, 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
case let .editablePublicLink(theme, strings, prefix, currentText, text):
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)
}, 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 {
let statusText: String
switch status {

View File

@ -22,7 +22,7 @@ private enum WatchSettingsSection: Int32 {
private enum WatchSettingsControllerEntry: ItemListNodeEntry {
case replyPresetsHeader(PresentationTheme, String)
case replyPreset(PresentationTheme, String, String, String, Int32)
case replyPreset(PresentationTheme, PresentationStrings, String, String, String, Int32)
case replyPresetsInfo(PresentationTheme, String)
var section: ItemListSectionId {
@ -36,7 +36,7 @@ private enum WatchSettingsControllerEntry: ItemListNodeEntry {
switch self {
case .replyPresetsHeader:
return 0
case let .replyPreset(_, _, _, _, index):
case let .replyPreset(_, _, _, _, _, index):
return 1 + index
case .replyPresetsInfo:
return 100
@ -52,8 +52,8 @@ private enum WatchSettingsControllerEntry: ItemListNodeEntry {
return false
}
case let .replyPreset(lhsTheme, lhsIdentifier, lhsPlaceholder, lhsValue, lhsIndex):
if case let .replyPreset(rhsTheme, rhsIdentifier, rhsPlaceholder, rhsValue, rhsIndex) = rhs, lhsTheme === rhsTheme, lhsIdentifier == rhsIdentifier, lhsPlaceholder == rhsPlaceholder, lhsValue == rhsValue, lhsIndex == rhsIndex {
case let .replyPreset(lhsTheme, lhsStrings, lhsIdentifier, lhsPlaceholder, lhsValue, lhsIndex):
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
} else {
return false
@ -76,8 +76,8 @@ private enum WatchSettingsControllerEntry: ItemListNodeEntry {
switch self {
case let .replyPresetsHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .replyPreset(theme, 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
case let .replyPreset(theme, strings, identifier, placeholder, value, _):
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))
}, action: {})
case let .replyPresetsInfo(theme, text):
@ -102,7 +102,7 @@ private func watchSettingsControllerEntries(presentationData: PresentationData,
entries.append(.replyPresetsHeader(presentationData.theme, presentationData.strings.AppleWatch_ReplyPresets))
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))