Voice Over Improvements

This commit is contained in:
Ilya Laktyushin 2021-02-12 21:12:53 +04:00
parent 660234168d
commit 0ac2026d5a
14 changed files with 3271 additions and 3199 deletions

Binary file not shown.

View File

@ -6069,6 +6069,8 @@ Sorry for the inconvenience.";
"BroadcastGroups.ConfirmationAlert.Convert" = "Convert";
"BroadcastGroups.LimitAlert.Title" = "Limit Reached";
"BroadcastGroups.LimitAlert.Text" = "Your group has reached a limit of **%@** members. You can increase this limit by converting the group to a **broadcast group** where only admins can post. Interested?";
"BroadcastGroups.LimitAlert.Text" = "Your group has reached a limit of **%@** members.\n\nYou can increase this limit by converting the group to a **broadcast group** where only admins can post. Interested?";
"BroadcastGroups.LimitAlert.LearnMore" = "Learn More";
"BroadcastGroups.LimitAlert.SettingsTip" = "If you change your mind, go to the settings of your group.";
"VoiceOver.AuthSessions.CurrentSession" = "Current Session";

View File

@ -78,6 +78,8 @@ private final class ItemNode: ASDisplayNode {
private var deleteButtonNode: ItemNodeDeleteButtonNode?
private let buttonNode: HighlightTrackingButtonNode
private let activateArea: AccessibilityAreaNode
private var selectionFraction: CGFloat = 0.0
private(set) var unreadCount: Int = 0
@ -136,6 +138,8 @@ private final class ItemNode: ASDisplayNode {
self.buttonNode = HighlightTrackingButtonNode()
self.activateArea = AccessibilityAreaNode()
super.init()
self.extractedContainerNode.contentNode.addSubnode(self.extractedBackgroundNode)
@ -154,6 +158,8 @@ private final class ItemNode: ASDisplayNode {
self.containerNode.addSubnode(self.extractedContainerNode)
self.containerNode.targetNodeForActivationProgress = self.extractedContainerNode.contentNode
self.addSubnode(self.containerNode)
self.addSubnode(self.activateArea)
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
@ -184,7 +190,7 @@ private final class ItemNode: ASDisplayNode {
self.pressed()
}
func updateText(title: String, shortTitle: String, unreadCount: Int, unreadHasUnmuted: Bool, isNoFilter: Bool, selectionFraction: CGFloat, isEditing: Bool, isAllChats: Bool, isReordering: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
func updateText(strings: PresentationStrings, title: String, shortTitle: String, unreadCount: Int, unreadHasUnmuted: Bool, isNoFilter: Bool, selectionFraction: CGFloat, isEditing: Bool, isAllChats: Bool, isReordering: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
if self.theme !== presentationData.theme {
self.theme = presentationData.theme
@ -192,6 +198,13 @@ private final class ItemNode: ASDisplayNode {
self.badgeBackgroundInactiveNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: presentationData.theme.chatList.unreadBadgeInactiveBackgroundColor)
}
self.activateArea.accessibilityLabel = title
if unreadCount > 0 {
self.activateArea.accessibilityValue = strings.VoiceOver_Chat_UnreadMessages(Int32(unreadCount))
} else {
self.activateArea.accessibilityValue = ""
}
self.containerNode.isGestureEnabled = !isEditing && !isReordering
self.buttonNode.isUserInteractionEnabled = !isEditing && !isReordering
@ -308,11 +321,12 @@ private final class ItemNode: ASDisplayNode {
transition.updateAlpha(node: self.shortTitleContainer, alpha: useShortTitle ? 1.0 : 0.0)
self.buttonNode.frame = CGRect(origin: CGPoint(x: -sideInset, y: 0.0), size: CGSize(width: size.width + sideInset * 2.0, height: size.height))
self.extractedContainerNode.frame = CGRect(origin: CGPoint(), size: size)
self.extractedContainerNode.contentNode.frame = CGRect(origin: CGPoint(), size: size)
self.extractedContainerNode.contentRect = CGRect(origin: CGPoint(x: self.extractedBackgroundNode.frame.minX, y: 0.0), size: CGSize(width:self.extractedBackgroundNode.frame.width, height: size.height))
self.containerNode.frame = CGRect(origin: CGPoint(), size: size)
self.activateArea.frame = CGRect(origin: CGPoint(), size: size)
self.hitTestSlop = UIEdgeInsets(top: 0.0, left: -sideInset, bottom: 0.0, right: -sideInset)
self.extractedContainerNode.hitTestSlop = self.hitTestSlop
@ -738,7 +752,7 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
selectionFraction = 0.0
}
itemNode.updateText(title: filter.title(strings: presentationData.strings), shortTitle: filter.shortTitle(strings: presentationData.strings), unreadCount: unreadCount, unreadHasUnmuted: unreadHasUnmuted, isNoFilter: isNoFilter, selectionFraction: selectionFraction, isEditing: false, isAllChats: isNoFilter, isReordering: isEditing || isReordering, presentationData: presentationData, transition: itemNodeTransition)
itemNode.updateText(strings: presentationData.strings, title: filter.title(strings: presentationData.strings), shortTitle: filter.shortTitle(strings: presentationData.strings), unreadCount: unreadCount, unreadHasUnmuted: unreadHasUnmuted, isNoFilter: isNoFilter, selectionFraction: selectionFraction, isEditing: false, isAllChats: isNoFilter, isReordering: isEditing || isReordering, presentationData: presentationData, transition: itemNodeTransition)
}
var removeKeys: [ChatListFilterTabEntryId] = []
for (id, _) in self.itemNodes {

View File

@ -4024,7 +4024,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
}
}
public func ensureItemNodeVisible(_ node: ListViewItemNode, animated: Bool = true, overflow: CGFloat = 0.0, curve: ListViewAnimationCurve = .Default(duration: 0.25)) {
public func ensureItemNodeVisible(_ node: ListViewItemNode, animated: Bool = true, overflow: CGFloat = 0.0, allowIntersection: Bool = false, curve: ListViewAnimationCurve = .Default(duration: 0.25)) {
if let index = node.index {
if node.apparentHeight > self.visibleSize.height - self.insets.top - self.insets.bottom {
if node.frame.maxY > self.visibleSize.height - self.insets.bottom {
@ -4037,9 +4037,13 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.visible, animated: animated, curve: ListViewAnimationCurve.Default(duration: nil), directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
} else {
if node.frame.minY < self.insets.top {
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.top(overflow), animated: animated, curve: curve, directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
if !allowIntersection || node.frame.maxY < self.insets.top {
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.top(overflow), animated: animated, curve: curve, directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
}
} else if node.frame.maxY > self.visibleSize.height - self.insets.bottom {
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.bottom(-overflow), animated: animated, curve: curve, directionHint: ListViewScrollToItemDirectionHint.Down), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
if !allowIntersection || node.frame.minY > self.visibleSize.height - self.insets.bottom {
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.bottom(-overflow), animated: animated, curve: curve, directionHint: ListViewScrollToItemDirectionHint.Down), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
}
}
}
}

View File

@ -542,7 +542,7 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode {
}
override open func accessibilityElementDidBecomeFocused() {
(self.supernode as? ListView)?.ensureItemNodeVisible(self, animated: false, overflow: 22.0)
(self.supernode as? ListView)?.ensureItemNodeVisible(self, animated: false, overflow: 22.0, allowIntersection: true)
}
public func updateFrame(_ frame: CGRect, within containerSize: CGSize) {

View File

@ -150,8 +150,6 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height))
strongSelf.activateArea.accessibilityLabel = attributedText.string
strongSelf.activateArea.accessibilityLabel = attributedText.string
let _ = titleApply()
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: topInset), size: titleLayout.size)

View File

@ -113,6 +113,8 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
private let locationNode: TextNode
private let labelNode: TextNode
private let activateArea: AccessibilityAreaNode
private var layoutParams: (ItemListRecentSessionItem, ListViewItemLayoutParams, ItemListNeighbors)?
private var editableControlNode: ItemListEditableControlNode?
@ -152,12 +154,16 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isLayerBacked = true
self.activateArea = AccessibilityAreaNode()
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
self.addSubnode(self.titleNode)
self.addSubnode(self.appNode)
self.addSubnode(self.locationNode)
self.addSubnode(self.labelNode)
self.addSubnode(self.activateArea)
}
func asyncLayout() -> (_ item: ItemListRecentSessionItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) {
@ -268,6 +274,33 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
if let strongSelf = self {
strongSelf.layoutParams = (item, params, neighbors)
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height))
var label = ""
if item.session.isCurrent {
label = item.presentationData.strings.VoiceOver_AuthSessions_CurrentSession
label += ", "
}
label += titleAttributedString?.string ?? ""
strongSelf.activateArea.accessibilityLabel = label
var value = ""
if let string = appAttributedString?.string {
value += string
}
if let string = locationAttributedString?.string {
if !value.isEmpty {
value += "\n"
}
value += string
}
if item.enabled {
strongSelf.activateArea.accessibilityTraits = []
} else {
strongSelf.activateArea.accessibilityTraits = .notEnabled
}
if let _ = updatedTheme {
strongSelf.topStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor
strongSelf.bottomStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor

View File

@ -30,7 +30,6 @@ public func channelAdmins(account: Account, peerId: PeerId) -> Signal<[RenderedC
if let peer = peers[participant.peerId] {
items.append(RenderedChannelParticipant(participant: participant, peer: peer, peers: peers, presences: presences))
}
}
return account.postbox.transaction { transaction -> [RenderedChannelParticipant] in

View File

@ -119,9 +119,9 @@ public final class PermissionContentNode: ASDisplayNode {
secondaryText = true
}
self.textNode.textAlignment = secondaryText ? .natural : .center
self.textNode.textAlignment = secondaryButtonTitle != nil ? .natural : .center
let body = MarkdownAttributeSet(font: Font.regular(16.0), textColor: secondaryText ? theme.list.itemSecondaryTextColor : theme.list.itemPrimaryTextColor)
let body = MarkdownAttributeSet(font: Font.regular(16.0), textColor: secondaryButtonTitle != nil ? theme.list.itemSecondaryTextColor : theme.list.itemPrimaryTextColor)
let link = MarkdownAttributeSet(font: Font.regular(16.0), textColor: theme.list.itemAccentColor, additionalAttributes: [TelegramTextAttributes.URL: ""])
self.textNode.attributedText = parseMarkdownIntoAttributedString(text.replacingOccurrences(of: "]", with: "]()"), attributes: MarkdownAttributes(body: body, bold: body, link: link, linkAttribute: { _ in nil }), textAlignment: secondaryText ? .natural : .center)

View File

@ -94,6 +94,7 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode {
let labelNode: TextNode
let backgroundNode: ASImageNode
let stickBackgroundNode: ASImageNode
let activateArea: AccessibilityAreaNode
private let localTimestamp: Int32
private var presentationData: ChatPresentationData
@ -157,6 +158,9 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode {
}
self.text = text
self.activateArea = AccessibilityAreaNode()
self.activateArea.accessibilityTraits = .staticText
super.init(layerBacked: false, dynamicBounce: true, isRotated: true, seeThrough: false)
self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
@ -170,11 +174,15 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode {
self.addSubnode(self.backgroundNode)
self.addSubnode(self.labelNode)
self.addSubnode(self.activateArea)
let titleFont = Font.medium(min(18.0, floor(presentationData.fontSize.baseDisplaySize * 13.0 / 17.0)))
let attributedString = NSAttributedString(string: text, font: titleFont, textColor: bubbleVariableColor(variableColor: presentationData.theme.theme.chat.serviceMessage.dateTextColor, wallpaper: presentationData.theme.wallpaper))
let labelLayout = TextNode.asyncLayout(self.labelNode)
self.activateArea.accessibilityLabel = text
let (size, apply) = labelLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 320.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let _ = apply()
self.labelNode.frame = CGRect(origin: CGPoint(), size: size.size)
@ -230,6 +238,8 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode {
self.stickBackgroundNode.frame = CGRect(origin: CGPoint(), size: backgroundFrame.size)
self.backgroundNode.frame = backgroundFrame
self.labelNode.frame = CGRect(origin: CGPoint(x: backgroundFrame.origin.x + chatDateInset, y: backgroundFrame.origin.y + floorToScreenPixels((backgroundSize.height - labelSize.height) / 2.0)), size: labelSize)
self.activateArea.frame = backgroundFrame
}
override func updateStickDistanceFactor(_ factor: CGFloat, transition: ContainedViewLayoutTransition) {

View File

@ -59,6 +59,8 @@ class ChatUnreadItemNode: ListViewItemNode {
let backgroundNode: ASImageNode
let labelNode: TextNode
let activateArea: AccessibilityAreaNode
private var theme: ChatPresentationThemeData?
private let layoutConstants = ChatMessageItemLayoutConstants.default
@ -71,12 +73,17 @@ class ChatUnreadItemNode: ListViewItemNode {
self.labelNode = TextNode()
self.labelNode.isUserInteractionEnabled = false
self.activateArea = AccessibilityAreaNode()
self.activateArea.accessibilityTraits = .staticText
super.init(layerBacked: false, dynamicBounce: true, rotated: true)
self.addSubnode(self.backgroundNode)
self.addSubnode(self.labelNode)
self.addSubnode(self.activateArea)
self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
self.scrollPositioningInsets = UIEdgeInsets(top: 5.0, left: 0.0, bottom: 6.0, right: 0.0)
@ -114,7 +121,8 @@ class ChatUnreadItemNode: ListViewItemNode {
updatedBackgroundImage = PresentationResourcesChat.chatUnreadBarBackgroundImage(item.presentationData.theme.theme)
}
let (size, apply) = labelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Conversation_UnreadMessages, font: titleFont, textColor: item.presentationData.theme.theme.chat.serviceMessage.unreadBarTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let string = item.presentationData.strings.Conversation_UnreadMessages
let (size, apply) = labelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: string, font: titleFont, textColor: item.presentationData.theme.theme.chat.serviceMessage.unreadBarTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let backgroundSize = CGSize(width: params.width, height: 25.0)
@ -129,6 +137,9 @@ class ChatUnreadItemNode: ListViewItemNode {
let _ = apply()
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: backgroundSize)
strongSelf.activateArea.accessibilityLabel = string
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: backgroundSize)
strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - size.size.width) / 2.0), y: floorToScreenPixels((backgroundSize.height - size.size.height) / 2.0)), size: size.size)
}

View File

@ -224,7 +224,7 @@ public class ComposeControllerImpl: ViewController, ComposeController {
if let strongSelf = self {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let controller = PermissionController(context: strongSelf.context, splashScreen: true)
controller.setState(.custom(icon: .animation("Channels"), title: presentationData.strings.ChannelIntro_Title, subtitle: nil, text: presentationData.strings.ChannelIntro_Text, buttonTitle: presentationData.strings.ChannelIntro_CreateChannel, secondaryButtonTitle: nil, footerText: nil), animated: false)
controller.setState(.custom(icon: .animation("Channel"), title: presentationData.strings.ChannelIntro_Title, subtitle: nil, text: presentationData.strings.ChannelIntro_Text, buttonTitle: presentationData.strings.ChannelIntro_CreateChannel, secondaryButtonTitle: nil, footerText: nil), animated: false)
controller.proceed = { [weak self] result in
if let strongSelf = self {
(strongSelf.navigationController as? NavigationController)?.replaceTopController(createChannelController(context: strongSelf.context), animated: true)