mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-24 09:06:30 +00:00
Improve VoiceOver
This commit is contained in:
parent
ac39dae178
commit
ad1dad4b58
@ -79,7 +79,6 @@
|
||||
buildConfiguration = "DebugHockeyapp"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
enableAddressSanitizer = "YES"
|
||||
enableASanStackUseAfterReturn = "YES"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ NSString *const TGMediaAssetsVideoCellKind = @"TGMediaAssetsVideoCellKind";
|
||||
_durationLabel.accessibilityIgnoresInvertColors = true;
|
||||
}
|
||||
|
||||
self.accessibilityLabel = @"Video";
|
||||
self.accessibilityLabel = TGLocalized(@"Message.Video");
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@ -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];
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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() {
|
||||
|
||||
@ -50,6 +50,8 @@ public final class CallController: ViewController {
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
self.isOpaqueWhenInOverlay = true
|
||||
|
||||
self.statusBar.statusBarStyle = .White
|
||||
self.statusBar.ignoreInCall = true
|
||||
|
||||
|
||||
@ -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 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -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
|
||||
})
|
||||
})
|
||||
|
||||
@ -189,6 +189,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
self.messageAccessibilityArea.focused = { [weak self] in
|
||||
self?.accessibilityElementDidBecomeFocused()
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
|
||||
|
||||
@ -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])
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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 = ""
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
Binary file not shown.
@ -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))
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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))
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user