Merge commit 'f80093608077ec9987c39fbe9a333412dd85dfa5'

This commit is contained in:
Ali 2023-02-08 00:03:30 +04:00
commit b876a5c829
119 changed files with 1154 additions and 650 deletions

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<device id="retina6_7" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
@ -10,27 +10,13 @@
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="O8c-13-3vw">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<rect key="frame" x="0.0" y="0.0" width="428" height="926"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Components/LaunchLogo" translatesAutoresizingMaskIntoConstraints="NO" id="Ra6-Gz-QsF">
<rect key="frame" x="138" y="284" width="99" height="99"/>
<constraints>
<constraint firstAttribute="width" constant="99" id="Mhj-0F-KYG"/>
<constraint firstAttribute="height" constant="99" id="gdP-J3-bQE"/>
</constraints>
</imageView>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="Ra6-Gz-QsF" firstAttribute="centerY" secondItem="O8c-13-3vw" secondAttribute="centerY" id="54l-Rw-Siu"/>
<constraint firstItem="Ra6-Gz-QsF" firstAttribute="centerX" secondItem="O8c-13-3vw" secondAttribute="centerX" id="zdX-Zd-oe6"/>
</constraints>
<point key="canvasLocation" x="139" y="117"/>
</view>
</objects>
<resources>
<image name="Components/LaunchLogo" width="99" height="99"/>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>

View File

@ -8886,3 +8886,25 @@ Sorry for the inconvenience.";
"Translation.Language.ta" = "Tamil";
"Translation.Language.tr" = "Turkish";
"Translation.Language.uz" = "Uzbek";
"Settings.RaiseToListen" = "Raise to Listen";
"Settings.RaiseToListenInfo" = "Raise to Listen allows you to quickly listen and reply to incoming audio messages by raising the phone to your ear.";
"Login.CodeSentCallText" = "Calling **%@** to dictate the code.";
"Premium.Purchase.OnlyOneSubscriptionAllowed" = "You have already purchased Telegram Premium for another account. You can only have one Telegram Premium subscription on one Apple ID.";
"Call.VoiceOver.Minimize" = "Minimize Call";
"Login.VoiceOver.PhoneCountryCode" = "Phone country code";
"Login.VoiceOver.PhoneNumber" = "Phone number";
"Login.VoiceOver.Password" = "Password";
"Gallery.VoiceOver.Delete" = "Delete";
"Gallery.VoiceOver.Fullscreen" = "Fullscreen";
"Gallery.VoiceOver.Share" = "Share";
"Gallery.VoiceOver.Edit" = "Edit";
"Gallery.VoiceOver.Stickers" = "Stickers";
"Gallery.VoiceOver.PictureInPicture" = "Picture-in-Picture";
"Appearance.VoiceOver.Theme" = "%@ Theme";

View File

@ -757,6 +757,8 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
self.scrollNode.view.delegate = self
self.scrollNode.view.showsHorizontalScrollIndicator = false
self.scrollNode.view.showsVerticalScrollIndicator = false
self.view.accessibilityTraits = .tabBar
}
@objc private func buttonPressed() {
@ -894,6 +896,26 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
containerSize: CGSize(width: buttonWidth, height: buttonSize.height)
)
buttonTransition.setFrame(view: buttonView, frame: buttonFrame)
var accessibilityTitle = ""
switch type {
case .gallery:
accessibilityTitle = self.presentationData.strings.Attachment_Gallery
case .file:
accessibilityTitle = self.presentationData.strings.Attachment_File
case .location:
accessibilityTitle = self.presentationData.strings.Attachment_Location
case .contact:
accessibilityTitle = self.presentationData.strings.Attachment_Contact
case .poll:
accessibilityTitle = self.presentationData.strings.Attachment_Poll
case let .app(_, appName, _):
accessibilityTitle = appName
case .standalone:
accessibilityTitle = ""
}
buttonView.isAccessibilityElement = true
buttonView.accessibilityLabel = accessibilityTitle
buttonView.accessibilityTraits = [.button]
}
}

View File

@ -22,11 +22,14 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
private let animationNode: AnimatedStickerNode
private let titleNode: ImmediateTextNode
private let titleActivateAreaNode: AccessibilityAreaNode
private let titleIconNode: ASImageNode
private let currentOptionNode: ImmediateTextNode
private let currentOptionActivateAreaNode: AccessibilityAreaNode
private var dustNode: InvisibleInkDustNode?
private let currentOptionInfoNode: ASTextNode
private let currentOptionInfoActivateAreaNode: AccessibilityAreaNode
private let nextOptionTitleNode: ImmediateTextNode
private let nextOptionButtonNode: HighlightableButtonNode
@ -91,6 +94,9 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
self.titleNode.isUserInteractionEnabled = false
self.titleNode.displaysAsynchronously = false
self.titleActivateAreaNode = AccessibilityAreaNode()
self.titleActivateAreaNode.accessibilityTraits = .staticText
self.titleIconNode = ASImageNode()
self.titleIconNode.isLayerBacked = true
self.titleIconNode.displayWithoutProcessing = true
@ -102,10 +108,16 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
self.currentOptionNode.lineSpacing = 0.1
self.currentOptionNode.maximumNumberOfLines = 0
self.currentOptionActivateAreaNode = AccessibilityAreaNode()
self.currentOptionActivateAreaNode.accessibilityTraits = .staticText
self.currentOptionInfoNode = ASTextNode()
self.currentOptionInfoNode.isUserInteractionEnabled = false
self.currentOptionInfoNode.displaysAsynchronously = false
self.currentOptionInfoActivateAreaNode = AccessibilityAreaNode()
self.currentOptionInfoActivateAreaNode.accessibilityTraits = .staticText
self.nextOptionTitleNode = ImmediateTextNode()
self.nextOptionButtonNode = HighlightableButtonNode()
@ -113,6 +125,12 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
let (nextOptionText, nextOptionActive) = authorizationNextOptionText(currentType: .sms(length: 5), nextType: .call, timeout: 60, strings: self.strings, primaryColor: self.theme.list.itemPrimaryTextColor, accentColor: self.theme.list.itemAccentColor)
self.nextOptionTitleNode.attributedText = nextOptionText
self.nextOptionButtonNode.isUserInteractionEnabled = nextOptionActive
self.nextOptionButtonNode.accessibilityLabel = nextOptionText.string
if nextOptionActive {
self.nextOptionButtonNode.accessibilityTraits = [.button]
} else {
self.nextOptionButtonNode.accessibilityTraits = [.button, .notEnabled]
}
self.nextOptionButtonNode.addSubnode(self.nextOptionTitleNode)
self.codeInputView = CodeInputView()
@ -156,8 +174,10 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
self.addSubnode(self.codeInputView)
self.addSubnode(self.titleNode)
self.addSubnode(self.titleActivateAreaNode)
self.addSubnode(self.titleIconNode)
self.addSubnode(self.currentOptionNode)
self.addSubnode(self.currentOptionActivateAreaNode)
self.addSubnode(self.currentOptionInfoNode)
self.addSubnode(self.nextOptionButtonNode)
self.addSubnode(self.animationNode)
@ -266,10 +286,18 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
self.appleSignInAllowed = appleSignInAllowed
self.currentOptionNode.attributedText = authorizationCurrentOptionText(codeType, phoneNumber: self.phoneNumber, email: self.email, strings: self.strings, primaryColor: self.theme.list.itemPrimaryTextColor, accentColor: self.theme.list.itemAccentColor)
self.currentOptionActivateAreaNode.accessibilityLabel = self.currentOptionNode.attributedText?.string ?? ""
if case .missedCall = codeType {
self.currentOptionInfoNode.attributedText = NSAttributedString(string: self.strings.Login_CodePhonePatternInfoText, font: Font.regular(17.0), textColor: self.theme.list.itemPrimaryTextColor, paragraphAlignment: .center)
self.currentOptionInfoActivateAreaNode.accessibilityLabel = self.currentOptionInfoNode.attributedText?.string ?? ""
if self.currentOptionInfoActivateAreaNode.supernode == nil {
self.addSubnode(self.currentOptionInfoActivateAreaNode)
}
} else {
self.currentOptionInfoNode.attributedText = NSAttributedString(string: "", font: Font.regular(17.0), textColor: self.theme.list.itemPrimaryTextColor)
if self.currentOptionInfoActivateAreaNode.supernode != nil {
self.currentOptionInfoActivateAreaNode.removeFromSupernode()
}
}
if let timeout = timeout {
#if DEBUG
@ -283,7 +311,12 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
let (nextOptionText, nextOptionActive) = authorizationNextOptionText(currentType: codeType, nextType: nextType, timeout: strongSelf.currentTimeoutTime, strings: strongSelf.strings, primaryColor: strongSelf.theme.list.itemPrimaryTextColor, accentColor: strongSelf.theme.list.itemAccentColor)
strongSelf.nextOptionTitleNode.attributedText = nextOptionText
strongSelf.nextOptionButtonNode.isUserInteractionEnabled = nextOptionActive
strongSelf.nextOptionButtonNode.accessibilityLabel = nextOptionText.string
if nextOptionActive {
strongSelf.nextOptionButtonNode.accessibilityTraits = [.button]
} else {
strongSelf.nextOptionButtonNode.accessibilityTraits = [.button, .notEnabled]
}
if let layoutArguments = strongSelf.layoutArguments {
strongSelf.containerLayoutUpdated(layoutArguments.0, navigationBarHeight: layoutArguments.1, transition: .immediate)
}
@ -301,7 +334,12 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
let (nextOptionText, nextOptionActive) = authorizationNextOptionText(currentType: codeType, nextType: nextType, timeout: self.currentTimeoutTime, strings: self.strings, primaryColor: self.theme.list.itemPrimaryTextColor, accentColor: self.theme.list.itemAccentColor)
self.nextOptionTitleNode.attributedText = nextOptionText
self.nextOptionButtonNode.isUserInteractionEnabled = nextOptionActive
self.nextOptionButtonNode.accessibilityLabel = nextOptionText.string
if nextOptionActive {
self.nextOptionButtonNode.accessibilityTraits = [.button]
} else {
self.nextOptionButtonNode.accessibilityTraits = [.button, .notEnabled]
}
if let layoutArguments = self.layoutArguments {
self.containerLayoutUpdated(layoutArguments.0, navigationBarHeight: layoutArguments.1, transition: .immediate)
}
@ -347,6 +385,8 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
self.titleNode.attributedText = NSAttributedString(string: self.strings.Login_EnterCodeTelegramTitle, font: Font.semibold(40.0), textColor: self.theme.list.itemPrimaryTextColor)
}
self.titleActivateAreaNode.accessibilityLabel = self.titleNode.attributedText?.string ?? ""
if let inputHeight = layout.inputHeight {
if let codeType = self.codeType, case .email = codeType {
insets.bottom = max(inputHeight, insets.bottom)
@ -525,6 +565,10 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
}
self.nextOptionTitleNode.frame = self.nextOptionButtonNode.bounds
self.titleActivateAreaNode.frame = self.titleNode.frame
self.currentOptionActivateAreaNode.frame = self.currentOptionNode.frame
self.currentOptionInfoActivateAreaNode.frame = self.currentOptionInfoNode.frame
}
func activateInput() {

View File

@ -954,7 +954,8 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
}
}
let signal = TGMediaVideoConverter.convert(avatarAsset, adjustments: avatarAdjustments, watcher: nil, entityRenderer: entityRenderer)!
let tempFile = EngineTempBox.shared.tempFile(fileName: "video.mp4")
let signal = TGMediaVideoConverter.convert(avatarAsset, adjustments: avatarAdjustments, path: tempFile.path, watcher: nil, entityRenderer: entityRenderer)!
let signalDisposable = signal.start(next: { next in
if let result = next as? TGMediaVideoConversionResult {
@ -964,6 +965,8 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
subscriber.putNext(resource)
EngineTempBox.shared.dispose(tempFile)
}
}
subscriber.putCompletion()

View File

@ -15,7 +15,9 @@ final class AuthorizationSequencePasswordEntryControllerNode: ASDisplayNode, UIT
private let animationNode: AnimatedStickerNode
private let titleNode: ASTextNode
private let titleActivateAreaNode: AccessibilityAreaNode
private let noticeNode: ASTextNode
private let noticeActivateAreaNode: AccessibilityAreaNode
private let forgotNode: HighlightableButtonNode
private let resetNode: HighlightableButtonNode
private let proceedNode: SolidRoundedButtonNode
@ -68,15 +70,23 @@ final class AuthorizationSequencePasswordEntryControllerNode: ASDisplayNode, UIT
self.titleNode.displaysAsynchronously = false
self.titleNode.attributedText = NSAttributedString(string: strings.LoginPassword_Title, font: Font.semibold(28.0), textColor: self.theme.list.itemPrimaryTextColor)
self.titleActivateAreaNode = AccessibilityAreaNode()
self.titleActivateAreaNode.accessibilityTraits = .staticText
self.noticeNode = ASTextNode()
self.noticeNode.isUserInteractionEnabled = false
self.noticeNode.displaysAsynchronously = false
self.noticeNode.lineSpacing = 0.1
self.noticeNode.attributedText = NSAttributedString(string: strings.TwoStepAuth_EnterPasswordHelp, font: Font.regular(17.0), textColor: self.theme.list.itemPrimaryTextColor, paragraphAlignment: .center)
self.noticeActivateAreaNode = AccessibilityAreaNode()
self.noticeActivateAreaNode.accessibilityTraits = .staticText
self.forgotNode = HighlightableButtonNode()
self.forgotNode.displaysAsynchronously = false
self.forgotNode.setAttributedTitle(NSAttributedString(string: self.strings.TwoStepAuth_EnterPasswordForgot, font: Font.regular(16.0), textColor: self.theme.list.itemAccentColor, paragraphAlignment: .center), for: [])
self.forgotNode.accessibilityLabel = self.strings.TwoStepAuth_EnterPasswordForgot
self.forgotNode.accessibilityTraits = [.button]
self.resetNode = HighlightableButtonNode()
self.resetNode.displaysAsynchronously = false
@ -95,6 +105,7 @@ final class AuthorizationSequencePasswordEntryControllerNode: ASDisplayNode, UIT
self.codeField.textField.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance
self.codeField.textField.disableAutomaticKeyboardHandling = [.forward, .backward]
self.codeField.textField.tintColor = self.theme.list.itemAccentColor
self.codeField.textField.accessibilityHint = self.strings.Login_VoiceOver_Password
self.proceedNode = SolidRoundedButtonNode(title: self.strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: self.theme), height: 50.0, cornerRadius: 11.0, gloss: false)
self.proceedNode.progressType = .embedded
@ -114,9 +125,11 @@ final class AuthorizationSequencePasswordEntryControllerNode: ASDisplayNode, UIT
self.addSubnode(self.codeSeparatorNode)
self.addSubnode(self.codeField)
self.addSubnode(self.titleNode)
self.addSubnode(self.titleActivateAreaNode)
self.addSubnode(self.forgotNode)
self.addSubnode(self.resetNode)
self.addSubnode(self.noticeNode)
self.addSubnode(self.noticeActivateAreaNode)
self.addSubnode(self.animationNode)
self.addSubnode(self.proceedNode)
@ -214,6 +227,12 @@ final class AuthorizationSequencePasswordEntryControllerNode: ASDisplayNode, UIT
self.animationNode.updateLayout(size: animationSize)
let _ = layoutAuthorizationItems(bounds: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top - insets.bottom - additionalBottomInset)), items: items, transition: transition, failIfDoesNotFit: false)
self.titleActivateAreaNode.accessibilityLabel = self.titleNode.attributedText?.string ?? ""
self.noticeActivateAreaNode.accessibilityLabel = self.noticeNode.attributedText?.string ?? ""
self.titleActivateAreaNode.frame = self.titleNode.frame
self.noticeActivateAreaNode.frame = self.noticeNode.frame
}
func activateInput() {

View File

@ -110,6 +110,8 @@ private final class PhoneAndCountryNode: ASDisplayNode {
self.phoneInputNode.numberField.textField.textColor = theme.list.itemPrimaryTextColor
self.phoneInputNode.countryCodeField.textField.tintColor = theme.list.itemAccentColor
self.phoneInputNode.numberField.textField.tintColor = theme.list.itemAccentColor
self.phoneInputNode.countryCodeField.accessibilityHint = strings.Login_VoiceOver_PhoneCountryCode
self.phoneInputNode.numberField.accessibilityHint = strings.Login_VoiceOver_PhoneNumber
self.phoneInputNode.countryCodeField.textField.tintColor = theme.list.itemAccentColor
self.phoneInputNode.numberField.textField.tintColor = theme.list.itemAccentColor
@ -172,6 +174,9 @@ private final class PhoneAndCountryNode: ASDisplayNode {
strongSelf.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: strings.Login_PhonePlaceholder, font: Font.regular(20.0), textColor: theme.list.itemPlaceholderTextColor)
}
strongSelf.countryButton.accessibilityLabel = strongSelf.countryButton.attributedTitle(for: .normal)?.string ?? ""
strongSelf.countryButton.accessibilityTraits = [.button]
if strongSelf.hasCountry {
strongSelf.hasNumberUpdated?(!strongSelf.phoneInputNode.codeAndNumber.2.isEmpty)
} else {
@ -289,7 +294,9 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
private let animationNode: AnimatedStickerNode
private let managedAnimationNode: ManagedPhoneAnimationNode
private let titleNode: ASTextNode
private let titleActivateAreaNode: AccessibilityAreaNode
private let noticeNode: ASTextNode
private let noticeActivateAreaNode: AccessibilityAreaNode
private let phoneAndCountryNode: PhoneAndCountryNode
private let contactSyncNode: ContactSyncNode
private let proceedNode: SolidRoundedButtonNode
@ -378,12 +385,18 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
self.titleNode.displaysAsynchronously = false
self.titleNode.attributedText = NSAttributedString(string: account == nil ? strings.Login_NewNumber : strings.Login_PhoneTitle, font: Font.light(30.0), textColor: theme.list.itemPrimaryTextColor)
self.titleActivateAreaNode = AccessibilityAreaNode()
self.titleActivateAreaNode.accessibilityTraits = .staticText
self.noticeNode = ASTextNode()
self.noticeNode.maximumNumberOfLines = 0
self.noticeNode.isUserInteractionEnabled = true
self.noticeNode.displaysAsynchronously = false
self.noticeNode.lineSpacing = 0.1
self.noticeActivateAreaNode = AccessibilityAreaNode()
self.noticeActivateAreaNode.accessibilityTraits = .staticText
self.noticeNode.attributedText = NSAttributedString(string: account == nil ? strings.ChangePhoneNumberNumber_Help : strings.Login_PhoneAndCountryHelp, font: Font.regular(17.0), textColor: theme.list.itemPrimaryTextColor, paragraphAlignment: .center)
self.contactSyncNode = ContactSyncNode(theme: theme, strings: strings)
@ -404,6 +417,8 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
self.addSubnode(self.titleNode)
self.addSubnode(self.noticeNode)
self.addSubnode(self.titleActivateAreaNode)
self.addSubnode(self.noticeActivateAreaNode)
self.addSubnode(self.phoneAndCountryNode)
self.addSubnode(self.contactSyncNode)
self.addSubnode(self.proceedNode)
@ -534,6 +549,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
let additionalBottomInset: CGFloat = layout.size.width > 320.0 ? 80.0 : 10.0
self.titleNode.attributedText = NSAttributedString(string: self.account == nil ? strings.Login_NewNumber : strings.Login_PhoneTitle, font: Font.bold(28.0), textColor: self.theme.list.itemPrimaryTextColor)
self.titleActivateAreaNode.accessibilityLabel = self.titleNode.attributedText?.string ?? ""
let inset: CGFloat = 24.0
let maximumWidth: CGFloat = min(430.0, layout.size.width)
@ -587,6 +603,10 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
let _ = layoutAuthorizationItems(bounds: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top - insets.bottom - additionalBottomInset)), items: items, transition: transition, failIfDoesNotFit: false)
transition.updateFrame(node: self.managedAnimationNode, frame: self.animationNode.frame)
self.titleActivateAreaNode.frame = self.titleNode.frame
self.noticeActivateAreaNode.accessibilityLabel = self.noticeNode.attributedText?.string ?? ""
self.noticeActivateAreaNode.frame = self.noticeNode.frame
}
func activateInput() {
@ -726,6 +746,7 @@ final class PhoneConfirmationController: ViewController {
class Node: ASDisplayNode {
private let theme: PresentationTheme
private let strings: PresentationStrings
private let code: String
private let number: String
@ -740,6 +761,7 @@ final class PhoneConfirmationController: ViewController {
private let phoneTargetNode: ImmediateTextNode
private let textNode: ImmediateTextNode
private let textActivateAreaNode: AccessibilityAreaNode
private let cancelButton: HighlightableButtonNode
fileprivate let proceedNode: SolidRoundedButtonNode
@ -751,6 +773,7 @@ final class PhoneConfirmationController: ViewController {
init(theme: PresentationTheme, strings: PresentationStrings, code: String, number: String) {
self.theme = theme
self.strings = strings
self.code = code
self.number = number
@ -767,8 +790,13 @@ final class PhoneConfirmationController: ViewController {
self.textNode.attributedText = NSAttributedString(string: strings.Login_PhoneNumberConfirmation, font: Font.regular(17.0), textColor: theme.list.itemPrimaryTextColor)
self.textNode.textAlignment = .center
self.textActivateAreaNode = AccessibilityAreaNode()
self.textActivateAreaNode.accessibilityTraits = .staticText
self.cancelButton = HighlightableButtonNode()
self.cancelButton.setTitle(strings.Login_Edit, with: Font.regular(19.0), with: theme.list.itemAccentColor, for: .normal)
self.cancelButton.accessibilityTraits = [.button]
self.cancelButton.accessibilityLabel = strings.Login_Edit
self.proceedNode = SolidRoundedButtonNode(title: strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: theme), height: 50.0, cornerRadius: 11.0, gloss: false)
self.proceedNode.progressType = .embedded
@ -814,6 +842,7 @@ final class PhoneConfirmationController: ViewController {
self.addSubnode(self.phoneTargetNode)
self.addSubnode(self.textNode)
self.addSubnode(self.textActivateAreaNode)
self.addSubnode(self.cancelButton)
self.addSubnode(self.proceedNode)
@ -1009,6 +1038,8 @@ final class PhoneConfirmationController: ViewController {
let textSize = self.textNode.updateLayout(backgroundSize)
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - textSize.width) / 2.0), y: 88.0), size: textSize).offsetBy(dx: backgroundFrame.minX, dy: backgroundFrame.minY))
self.textActivateAreaNode.frame = self.textNode.frame
self.textActivateAreaNode.accessibilityLabel = "\(self.code) \(self.number). \(self.strings.Login_PhoneNumberConfirmation)"
let proceedWidth = backgroundSize.width - 16.0 * 2.0
let proceedHeight = self.proceedNode.updateLayout(width: proceedWidth, transition: transition)

View File

@ -22,9 +22,9 @@ public func authorizationCurrentOptionText(_ type: SentAuthorizationCodeType, ph
let bold = MarkdownAttributeSet(font: Font.semibold(fontSize), textColor: primaryColor)
return parseMarkdownIntoAttributedString(strings.Login_ShortCallTitle, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in nil }), textAlignment: .center)
case .call:
return NSAttributedString(string: strings.Login_CodeSentCall, font: Font.regular(fontSize), textColor: primaryColor, paragraphAlignment: .center)
return parseMarkdownIntoAttributedString(strings.Login_CodeSentCallText(phoneNumber).string, attributes: attributes, textAlignment: .center)
case .flashCall:
return NSAttributedString(string: strings.ChangePhoneNumberCode_Called, font: Font.regular(fontSize), textColor: primaryColor, paragraphAlignment: .center)
return parseMarkdownIntoAttributedString(strings.Login_CodeSentCallText(phoneNumber).string, attributes: attributes, textAlignment: .center)
case .emailSetupRequired:
return NSAttributedString(string: "", font: Font.regular(fontSize), textColor: primaryColor, paragraphAlignment: .center)
case let .email(emailPattern, _, _, _, _):

View File

@ -76,9 +76,7 @@ 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
@ -140,13 +138,9 @@ private final class ItemNode: ASDisplayNode {
self.badgeBackgroundInactiveNode.displayWithoutProcessing = true
self.buttonNode = HighlightTrackingButtonNode()
self.activateArea = AccessibilityAreaNode()
super.init()
self.isAccessibilityElement = true
self.extractedContainerNode.contentNode.addSubnode(self.extractedBackgroundNode)
self.extractedContainerNode.contentNode.addSubnode(self.titleContainer)
self.titleContainer.addSubnode(self.titleNode)
@ -163,9 +157,7 @@ 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)
self.containerNode.activated = { [weak self] gesture, _ in
@ -212,11 +204,16 @@ private final class ItemNode: ASDisplayNode {
self.badgeBackgroundInactiveNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: presentationData.theme.chatList.unreadBadgeInactiveBackgroundColor)
}
self.activateArea.accessibilityLabel = title
self.buttonNode.accessibilityLabel = title
if unreadCount > 0 {
self.activateArea.accessibilityValue = strings.VoiceOver_Chat_UnreadMessages(Int32(unreadCount))
self.buttonNode.accessibilityValue = strings.VoiceOver_Chat_UnreadMessages(Int32(unreadCount))
} else {
self.activateArea.accessibilityValue = ""
self.buttonNode.accessibilityValue = ""
}
if selectionFraction == 1.0 {
self.buttonNode.accessibilityTraits = [.button, .selected]
} else {
self.buttonNode.accessibilityTraits = [.button]
}
self.containerNode.isGestureEnabled = !isEditing && !isReordering
@ -338,7 +335,6 @@ private final class ItemNode: ASDisplayNode {
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

View File

@ -126,6 +126,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
private var selectedFilterPromise = Promise<ChatListSearchFilterEntry?>()
private var transitionFraction: CGFloat = 0.0
private var appearanceTimestamp: Double?
private weak var copyProtectionTooltipController: TooltipController?
private lazy var hapticFeedback = { HapticFeedback() }()
@ -306,6 +308,10 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
return
}
if let appearanceTimestamp = strongSelf.appearanceTimestamp, CACurrentMediaTime() - appearanceTimestamp < 0.5 {
return
}
var key: ChatListSearchPaneKey?
var date = strongSelf.currentSearchOptions.date
var peer = strongSelf.currentSearchOptions.peer
@ -667,6 +673,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
if isFirstTime {
self.filterContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.appearanceTimestamp = CACurrentMediaTime()
}
var bottomIntrinsicInset = layout.intrinsicInsets.bottom

View File

@ -126,6 +126,13 @@ private final class ItemNode: ASDisplayNode {
transition.updateAlpha(node: self.titleNode, alpha: deselectionAlpha)
transition.updateAlpha(node: self.titleActiveNode, alpha: selectionAlpha)
self.buttonNode.accessibilityLabel = title
if selectionFraction == 1.0 {
self.buttonNode.accessibilityTraits = [.button, .selected]
} else {
self.buttonNode.accessibilityTraits = [.button]
}
if self.theme !== presentationData.theme {
self.theme = presentationData.theme
self.iconNode.image = icon

View File

@ -196,7 +196,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
}
return ContactsPeerItem(
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings),
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder),
sortOrder: nameSortOrder,
displayOrder: nameDisplayOrder,
context: context,

View File

@ -1848,7 +1848,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
entities = translation.entities
}
messageString = stringWithAppliedEntities(trimToLineCount(messageText, lineCount: authorAttributedString == nil ? 2 : 1), entities: entities, baseColor: theme.messageTextColor, linkColor: theme.messageTextColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: italicTextFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: message._asMessage())
messageString = foldLineBreaks(stringWithAppliedEntities(messageText, entities: entities, baseColor: theme.messageTextColor, linkColor: theme.messageTextColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: italicTextFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: message._asMessage()))
} else if spoilers != nil || customEmojiRanges != nil {
let mutableString = NSMutableAttributedString(string: messageText, font: textFont, textColor: theme.messageTextColor)
if let spoilers = spoilers {

View File

@ -319,7 +319,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
}
}
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListAdditionalCategoryItem(
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings),
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder),
context: context,
title: title,
image: image,
@ -525,7 +525,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
}
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings),
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder),
sortOrder: presentationData.nameSortOrder,
displayOrder: presentationData.nameDisplayOrder,
context: context,
@ -564,7 +564,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
let status: ContactsPeerItemStatus = .none
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings),
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder),
sortOrder: presentationData.nameSortOrder,
displayOrder: presentationData.nameDisplayOrder,
context: context,
@ -778,7 +778,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
}
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings),
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder),
sortOrder: presentationData.nameSortOrder,
displayOrder: presentationData.nameDisplayOrder,
context: context,
@ -817,7 +817,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
let status: ContactsPeerItemStatus = .none
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings),
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder),
sortOrder: presentationData.nameSortOrder,
displayOrder: presentationData.nameDisplayOrder,
context: context,
@ -887,7 +887,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
}
}
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListAdditionalCategoryItem(
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings),
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder),
context: context,
title: title,
image: image,
@ -1861,6 +1861,9 @@ public final class ChatListNode: ListView {
return false
}
}
case .GroupReferenceEntry:
isEmpty = false
return true
default:
return true
}

View File

@ -686,9 +686,9 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
} else if case let .peerType(type) = mode, !result.isEmpty {
switch type {
case .group:
result.append(.AdditionalCategory(index: 0, id: 0, title: "Create a New Group for This", image: PresentationResourcesItemList.createGroupIcon(state.presentationData.theme), appearance: .action, selected: false, presentationData: state.presentationData))
result.append(.AdditionalCategory(index: 0, id: 0, title: state.presentationData.strings.RequestPeer_CreateNewGroup, image: PresentationResourcesItemList.createGroupIcon(state.presentationData.theme), appearance: .action, selected: false, presentationData: state.presentationData))
case .channel:
result.append(.AdditionalCategory(index: 0, id: 0, title: "Create a New Channel for This", image: PresentationResourcesItemList.createGroupIcon(state.presentationData.theme), appearance: .action, selected: false, presentationData: state.presentationData))
result.append(.AdditionalCategory(index: 0, id: 0, title: state.presentationData.strings.RequestPeer_CreateNewChannel, image: PresentationResourcesItemList.createGroupIcon(state.presentationData.theme), appearance: .action, selected: false, presentationData: state.presentationData))
default:
break
}

View File

@ -103,7 +103,6 @@ public enum ChatInputMode: Equatable {
public enum ChatTitlePanelContext: Equatable, Comparable {
case pinnedMessage
case chatInfo
case requestInProgress
case toastAlert(String)
case inviteRequests([EnginePeer], Int32)
@ -112,8 +111,6 @@ public enum ChatTitlePanelContext: Equatable, Comparable {
switch self {
case .pinnedMessage:
return 0
case .chatInfo:
return 1
case .requestInProgress:
return 2
case .toastAlert:

View File

@ -82,6 +82,8 @@ private final class SortHeaderButton: HighlightableButtonNode {
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: 44.0))
self.referenceNode.frame = self.containerNode.bounds
self.accessibilityLabel = strings.Contacts_Sort
}
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
@ -201,6 +203,7 @@ public class ContactsController: ViewController {
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customDisplayNode: self.sortButton)
self.navigationItem.leftBarButtonItem?.accessibilityLabel = self.presentationData.strings.Contacts_Sort
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationAddIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.addPressed))
self.navigationItem.rightBarButtonItem?.accessibilityLabel = self.presentationData.strings.Contacts_VoiceOver_AddContact

View File

@ -101,6 +101,7 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin
self.isAccessibilityElement = true
self.accessibilityLabel = item.text
self.accessibilityTraits = [.button]
self.addSubnode(self.highlightBackgroundNode)
self.addSubnode(self.titleLabelNode)

View File

@ -64,7 +64,7 @@ func localizedCountryNamesAndCodes(strings: PresentationStrings) -> [((String, S
let locale = localeWithStrings(strings)
var result: [((String, String), String, [Int])] = []
for country in AuthorizationSequenceCountrySelectionController.countries() {
if country.hidden {
if country.hidden || country.id == "FT" {
continue
}
if let englishCountryName = usEnglishLocale.localizedString(forRegionCode: country.id), let countryName = locale.localizedString(forRegionCode: country.id) {
@ -360,20 +360,24 @@ final class AuthorizationSequenceCountrySelectionControllerNode: ASDisplayNode,
}
var countryName: String
var cleanCountryName: String
let originalCountryName: String
let code: String
if tableView === self.tableView {
countryName = self.sections[indexPath.section].1[indexPath.row].0.1
countryName = "\(emojiFlagForISOCountryCode(self.sections[indexPath.section].1[indexPath.row].1)) \(countryName)"
cleanCountryName = self.sections[indexPath.section].1[indexPath.row].0.1
countryName = "\(emojiFlagForISOCountryCode(self.sections[indexPath.section].1[indexPath.row].1)) \(cleanCountryName)"
originalCountryName = self.sections[indexPath.section].1[indexPath.row].0.0
code = "+\(self.sections[indexPath.section].1[indexPath.row].2)"
} else {
countryName = self.searchResults[indexPath.row].0.1
countryName = "\(emojiFlagForISOCountryCode(self.searchResults[indexPath.row].1)) \(countryName)"
cleanCountryName = self.searchResults[indexPath.row].0.1
countryName = "\(emojiFlagForISOCountryCode(self.searchResults[indexPath.row].1)) \(cleanCountryName)"
originalCountryName = self.searchResults[indexPath.row].0.0
code = "+\(self.searchResults[indexPath.row].2)"
}
cell.accessibilityLabel = cleanCountryName
cell.accessibilityValue = code
cell.textLabel?.text = countryName
cell.detailTextLabel?.text = originalCountryName
if self.displayCodes, let label = cell.accessoryView as? UILabel {

View File

@ -8,6 +8,8 @@ public protocol AccessibilityFocusableNode {
public final class AccessibilityAreaNode: ASDisplayNode {
public var activate: (() -> Bool)?
public var increment: (() -> Void)?
public var decrement: (() -> Void)?
public var focused: (() -> Void)?
override public init() {
@ -43,4 +45,12 @@ public final class AccessibilityAreaNode: ASDisplayNode {
}
}
}
override public func accessibilityIncrement() {
self.increment?()
}
override public func accessibilityDecrement() {
self.decrement?()
}
}

View File

@ -697,9 +697,16 @@ open class NavigationBar: ASDisplayNode {
if self.rightButtonNode.supernode != nil {
addAccessibilityChildren(of: self.rightButtonNode, container: self, to: &accessibilityElements)
}
if let customHeaderContentView = self.customHeaderContentView, customHeaderContentView.superview != nil {
customHeaderContentView.accessibilityFrame = UIAccessibility.convertToScreenCoordinates(customHeaderContentView.bounds, in: customHeaderContentView)
accessibilityElements.append(customHeaderContentView)
}
if let contentNode = self.contentNode {
addAccessibilityChildren(of: contentNode, container: self, to: &accessibilityElements)
}
if let secondaryContentNode = self.secondaryContentNode {
addAccessibilityChildren(of: secondaryContentNode, container: self, to: &accessibilityElements)
}
return accessibilityElements
} set(value) {
}

View File

@ -48,7 +48,9 @@ private final class NavigationButtonItemNode: ImmediateTextNode {
self.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState())
if _image == nil {
self.item?.accessibilityLabel = value
if self.item?.accessibilityLabel == nil {
self.item?.accessibilityLabel = value
}
}
}
}

View File

@ -341,6 +341,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
self.authorNameNode.maximumNumberOfLines = 1
self.authorNameNode.isUserInteractionEnabled = false
self.authorNameNode.displaysAsynchronously = false
self.dateNode = ASTextNode()
self.dateNode.maximumNumberOfLines = 1
self.dateNode.isUserInteractionEnabled = false
@ -428,9 +429,20 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
self.contentNode.addSubnode(self.statusButtonNode)
self.deleteButton.addTarget(self, action: #selector(self.deleteButtonPressed), for: [.touchUpInside])
self.deleteButton.accessibilityTraits = [.button]
self.deleteButton.accessibilityLabel = presentationData.strings.Gallery_VoiceOver_Delete
self.fullscreenButton.addTarget(self, action: #selector(self.fullscreenButtonPressed), for: [.touchUpInside])
self.fullscreenButton.accessibilityTraits = [.button]
self.fullscreenButton.accessibilityLabel = presentationData.strings.Gallery_VoiceOver_Fullscreen
self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), for: [.touchUpInside])
self.actionButton.accessibilityTraits = [.button]
self.actionButton.accessibilityLabel = presentationData.strings.Gallery_VoiceOver_Share
self.editButton.addTarget(self, action: #selector(self.editButtonPressed), for: [.touchUpInside])
self.editButton.accessibilityTraits = [.button]
self.editButton.accessibilityLabel = presentationData.strings.Gallery_VoiceOver_Edit
self.backwardButton.addTarget(self, action: #selector(self.backwardButtonPressed), forControlEvents: .touchUpInside)
self.forwardButton.addTarget(self, action: #selector(self.forwardButtonPressed), forControlEvents: .touchUpInside)
@ -595,12 +607,15 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
} else {
self.authorNameNode.attributedText = nil
}
self.authorNameNode.accessibilityLabel = self.authorNameNode.attributedText?.string
if let dateText = dateText {
self.dateNode.attributedText = NSAttributedString(string: dateText, font: dateFont, textColor: .white)
} else {
self.dateNode.attributedText = nil
}
self.dateNode.accessibilityLabel = self.dateNode.attributedText?.string
self.requestLayout?(.immediate)
}
@ -763,8 +778,11 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
} else {
self.authorNameNode.attributedText = nil
}
self.authorNameNode.accessibilityLabel = self.authorNameNode.attributedText?.string
self.dateNode.attributedText = NSAttributedString(string: dateText, font: dateFont, textColor: .white)
self.dateNode.accessibilityLabel = self.dateNode.attributedText?.string
if canFullscreen {
self.fullscreenButton.isHidden = false
self.deleteButton.isHidden = true

View File

@ -528,4 +528,12 @@ open class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGesture
break
}
}
open override func accessibilityPerformEscape() -> Bool {
if let controller = self.galleryController() {
controller.dismiss(animated: true)
return true
}
return false
}
}

View File

@ -443,10 +443,12 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
var barButtonItems: [UIBarButtonItem] = []
if imageReference.media.flags.contains(.hasStickers) {
let rightBarButtonItem = UIBarButtonItem(image: generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/Stickers"), color: .white), style: .plain, target: self, action: #selector(self.openStickersButtonPressed))
rightBarButtonItem.accessibilityLabel = self.presentationData.strings.Gallery_VoiceOver_Stickers
barButtonItems.append(rightBarButtonItem)
}
if self.message != nil {
let moreMenuItem = UIBarButtonItem(customDisplayNode: self.moreBarButton)!
moreMenuItem.accessibilityLabel = self.presentationData.strings.Common_More
barButtonItems.append(moreMenuItem)
}
self._rightBarButtonItems.set(.single(barButtonItems))
@ -646,6 +648,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
var barButtonItems: [UIBarButtonItem] = []
if self.message != nil {
let moreMenuItem = UIBarButtonItem(customDisplayNode: self.moreBarButton)!
moreMenuItem.accessibilityLabel = self.presentationData.strings.Common_More
barButtonItems.append(moreMenuItem)
}
self._rightBarButtonItems.set(.single(barButtonItems))

View File

@ -1420,10 +1420,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
var barButtonItems: [UIBarButtonItem] = []
if hasLinkedStickers {
let rightBarButtonItem = UIBarButtonItem(image: generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/Stickers"), color: .white), style: .plain, target: self, action: #selector(self.openStickersButtonPressed))
rightBarButtonItem.accessibilityLabel = self.presentationData.strings.Gallery_VoiceOver_Stickers
barButtonItems.append(rightBarButtonItem)
}
if forceEnablePiP || (!isAnimated && !disablePlayerControls && !disablePictureInPicture) {
let rightBarButtonItem = UIBarButtonItem(image: pictureInPictureButtonImage, style: .plain, target: self, action: #selector(self.pictureInPictureButtonPressed))
rightBarButtonItem.accessibilityLabel = self.presentationData.strings.Gallery_VoiceOver_PictureInPicture
self.pictureInPictureButton = rightBarButtonItem
barButtonItems.append(rightBarButtonItem)
self.hasPictureInPicture = true
@ -1452,6 +1454,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
if hasMoreButton {
let moreMenuItem = UIBarButtonItem(customDisplayNode: self.moreBarButton)!
moreMenuItem.accessibilityLabel = self.presentationData.strings.Common_More
barButtonItems.append(moreMenuItem)
}
}

View File

@ -217,17 +217,15 @@ public final class HorizontalPeerItemNode: ListViewItemNode {
strongSelf.badgeBackgroundNode.isHidden = true
}
var verticalOffset: CGFloat = 0.0
let state: RecentStatusOnlineIconState
if case .actionSheet = item.mode {
state = .panel
verticalOffset -= 9.0
} else {
state = .regular
}
strongSelf.onlineNode.setImage(PresentationResourcesChatList.recentStatusOnlineIcon(item.theme, state: state), color: nil, transition: .immediate)
strongSelf.onlineNode.frame = CGRect(x: itemLayout.size.width - onlineLayout.width - 18.0, y: itemLayout.size.height - onlineLayout.height - 18.0 + verticalOffset, width: onlineLayout.width, height: onlineLayout.height)
strongSelf.onlineNode.frame = CGRect(x: itemLayout.size.width / 2.0 + 14.0, y: itemLayout.size.width - onlineLayout.height - 30.0, width: onlineLayout.width, height: onlineLayout.height)
let _ = badgeApply()
let _ = onlineApply(animateContent)

View File

@ -83,7 +83,7 @@ public final class InAppPurchaseManager: NSObject {
}
public func pricePerMonth(_ monthsCount: Int) -> String {
let price = self.skProduct.price.dividing(by: NSDecimalNumber(value: monthsCount)).prettyPrice().round(2)
let price = self.skProduct.price.dividing(by: NSDecimalNumber(value: monthsCount)).round(2)
return self.numberFormatter.string(from: price) ?? ""
}
@ -308,11 +308,17 @@ public final class InAppPurchaseManager: NSObject {
return signal
}
public func getValidTransactionIds() -> [String] {
public struct ReceiptPurchase: Equatable {
public let productId: String
public let transactionId: String
public let expirationDate: Date
}
public func getReceiptPurchases() -> [ReceiptPurchase] {
guard let data = getReceiptData(), let receipt = parseReceipt(data) else {
return []
}
return receipt.purchases.map { $0.transactionId }
return receipt.purchases.map { ReceiptPurchase(productId: $0.productId, transactionId: $0.transactionId, expirationDate: $0.expirationDate) }
}
}
@ -359,7 +365,6 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver {
switch transaction.transactionState {
case .purchased:
Logger.shared.log("InAppPurchaseManager", "Account \(accountPeerId), transaction \(transaction.transactionIdentifier ?? ""), original transaction \(transaction.original?.transactionIdentifier ?? "none") purchased")
transactionState = .purchased(transactionId: transaction.transactionIdentifier)
transactionsToAssign.append(transaction)
case .restored:

View File

@ -7,6 +7,7 @@ private struct Asn1Tag {
static let sequence: Int32 = 0x10
static let set: Int32 = 0x11
static let utf8String: Int32 = 0x0c
static let date: Int32 = 0x16
}
private struct Asn1Entry {
@ -124,10 +125,12 @@ struct Receipt {
fileprivate struct Tag {
static let productIdentifier: Int32 = 1702
static let transactionIdentifier: Int32 = 1703
static let expirationDate: Int32 = 1708
}
let productId: String
let transactionId: String
let expirationDate: Date
}
let purchases: [Purchase]
@ -192,6 +195,27 @@ func parseReceipt(_ data: Data) -> Receipt? {
return Receipt(purchases: purchases)
}
private func parseRfc3339Date(_ str: String) -> Date? {
let posixLocale = Locale(identifier: "en_US_POSIX")
let formatter1 = DateFormatter()
formatter1.locale = posixLocale
formatter1.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssX5"
formatter1.timeZone = TimeZone(secondsFromGMT: 0)
let result = formatter1.date(from: str)
if result != nil {
return result
}
let formatter2 = DateFormatter()
formatter2.locale = posixLocale
formatter2.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSSSSSX5"
formatter2.timeZone = TimeZone(secondsFromGMT: 0)
return formatter2.date(from: str)
}
private func parsePurchaseAttributes(_ data: Data) -> Receipt.Purchase? {
let root = parse(data)
guard root.tag == Asn1Tag.set else {
@ -200,6 +224,7 @@ private func parsePurchaseAttributes(_ data: Data) -> Receipt.Purchase? {
var productId: String?
var transactionId: String?
var expirationDate: Date?
let receiptAttributes = parseSequence(root.data)
for attribute in receiptAttributes {
@ -219,12 +244,16 @@ private func parsePurchaseAttributes(_ data: Data) -> Receipt.Purchase? {
let valEntry = parse(value)
guard valEntry.tag == Asn1Tag.utf8String else { return nil }
transactionId = String(bytes: valEntry.data, encoding: .utf8)
case Receipt.Purchase.Tag.expirationDate:
let valEntry = parse(value)
guard valEntry.tag == Asn1Tag.date else { return nil }
expirationDate = parseRfc3339Date(String(bytes: valEntry.data, encoding: .utf8) ?? "")
default:
break
}
}
guard let productId, let transactionId else {
guard let productId, let transactionId, let expirationDate else {
return nil
}
return Receipt.Purchase(productId: productId, transactionId: transactionId)
return Receipt.Purchase(productId: productId, transactionId: transactionId, expirationDate: expirationDate)
}

View File

@ -91,6 +91,7 @@ final class InstantPageNavigationBar: ASDisplayNode {
self.moreButton = HighlightableButtonNode()
self.actionButton = HighlightableButtonNode()
self.scrollToTopButton = HighlightableButtonNode()
self.scrollToTopButton.isAccessibilityElement = false
self.actionButton.setImage(actionImage, for: [])
self.intrinsicActionSize = CGSize(width: 44.0, height: 44.0)
@ -128,6 +129,9 @@ final class InstantPageNavigationBar: ASDisplayNode {
self.addSubnode(self.titleNode)
self.addSubnode(self.progressNode)
self.actionButton.accessibilityLabel = strings.KeyCommand_Share
self.backButton.accessibilityLabel = strings.Common_Back
self.backButton.addTarget(self, action: #selector(self.backPressed), forControlEvents: .touchUpInside)
self.actionButton.addTarget(self, action: #selector(self.actionPressed), forControlEvents: .touchUpInside)
self.moreButton.addTarget(self, action: #selector(self.morePressed), forControlEvents: .touchUpInside)

View File

@ -159,11 +159,13 @@ public final class ItemListPresentationData: Equatable {
public let theme: PresentationTheme
public let fontSize: PresentationFontSize
public let strings: PresentationStrings
public let nameDisplayOrder: PresentationPersonNameOrder
public init(theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings) {
public init(theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder) {
self.theme = theme
self.fontSize = fontSize
self.strings = strings
self.nameDisplayOrder = nameDisplayOrder
}
public static func ==(lhs: ItemListPresentationData, rhs: ItemListPresentationData) -> Bool {
@ -176,6 +178,9 @@ public final class ItemListPresentationData: Equatable {
if lhs.fontSize != rhs.fontSize {
return false
}
if lhs.nameDisplayOrder != rhs.nameDisplayOrder {
return false
}
return true
}
}
@ -226,6 +231,6 @@ public extension PresentationFontSize {
public extension ItemListPresentationData {
convenience init(_ presentationData: PresentationData) {
self.init(theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings)
self.init(theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder)
}
}

View File

@ -430,9 +430,9 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.activateArea.accessibilityLabel = item.title
strongSelf.activateArea.accessibilityValue = item.label
if item.enabled {
strongSelf.activateArea.accessibilityTraits = []
strongSelf.activateArea.accessibilityTraits = [.button]
} else {
strongSelf.activateArea.accessibilityTraits = .notEnabled
strongSelf.activateArea.accessibilityTraits = [.button, .notEnabled]
}
if let icon = item.icon {

View File

@ -16,11 +16,11 @@
@interface TGMediaVideoConverter : NSObject
+ (SSignal *)convertAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher entityRenderer:(id<TGPhotoPaintEntityRenderer>)entityRenderer;
+ (SSignal *)convertAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher inhibitAudio:(bool)inhibitAudio entityRenderer:(id<TGPhotoPaintEntityRenderer>)entityRenderer;
+ (SSignal *)convertAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments path:(NSString *)path watcher:(TGMediaVideoFileWatcher *)watcher entityRenderer:(id<TGPhotoPaintEntityRenderer>)entityRenderer;
+ (SSignal *)convertAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments path:(NSString *)path watcher:(TGMediaVideoFileWatcher *)watcher inhibitAudio:(bool)inhibitAudio entityRenderer:(id<TGPhotoPaintEntityRenderer>)entityRenderer;
+ (SSignal *)hashForAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments;
+ (SSignal *)renderUIImage:(UIImage *)image duration:(NSTimeInterval)duration adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher entityRenderer:(id<TGPhotoPaintEntityRenderer>)entityRenderer;
+ (SSignal *)renderUIImage:(UIImage *)image duration:(NSTimeInterval)duration adjustments:(TGMediaVideoEditAdjustments *)adjustments path:(NSString *)path watcher:(TGMediaVideoFileWatcher *)watcher entityRenderer:(id<TGPhotoPaintEntityRenderer>)entityRenderer;
+ (NSUInteger)estimatedSizeForPreset:(TGMediaVideoConversionPreset)preset duration:(NSTimeInterval)duration hasAudio:(bool)hasAudio;
+ (TGMediaVideoConversionPreset)bestAvailablePresetForDimensions:(CGSize)dimensions;

View File

@ -43,6 +43,12 @@
- (void)setValue:(CGFloat)value animated:(BOOL)animated;
- (void)increase;
- (void)increaseBy:(CGFloat)delta;
- (void)decrease;
- (void)decreaseBy:(CGFloat)delta;
@end
extern const CGFloat TGPhotoEditorSliderViewMargin;

View File

@ -1475,6 +1475,8 @@
}
}
bool spoiler = [editingContext spoilerForItem:asset];
if ([asset isKindOfClass:[UIImage class]]) {
if (intent == TGMediaAssetsControllerSendFileIntent)
{
@ -1513,6 +1515,10 @@
if (groupedId != nil && !hasAnyTimers)
dict[@"groupedId"] = groupedId;
if (spoiler) {
dict[@"spoiler"] = @true;
}
id generatedItem = descriptionGenerator(dict, caption, nil, nil);
return generatedItem;
}];
@ -1560,6 +1566,10 @@
if (groupedId != nil && !hasAnyTimers)
dict[@"groupedId"] = groupedId;
if (spoiler) {
dict[@"spoiler"] = @true;
}
id generatedItem = descriptionGenerator(dict, caption, nil, nil);
return generatedItem;
}] catch:^SSignal *(__unused id error)
@ -1636,6 +1646,10 @@
else if (groupedId != nil && !hasAnyTimers)
dict[@"groupedId"] = groupedId;
if (spoiler) {
dict[@"spoiler"] = @true;
}
id generatedItem = descriptionGenerator(dict, caption, nil, nil);
return generatedItem;
}];

View File

@ -101,12 +101,12 @@
@implementation TGMediaVideoConverter
+ (SSignal *)convertAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher entityRenderer:(id<TGPhotoPaintEntityRenderer>)entityRenderer
+ (SSignal *)convertAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments path:(NSString *)path watcher:(TGMediaVideoFileWatcher *)watcher entityRenderer:(id<TGPhotoPaintEntityRenderer>)entityRenderer
{
return [self convertAVAsset:avAsset adjustments:adjustments watcher:watcher inhibitAudio:false entityRenderer:entityRenderer];
return [self convertAVAsset:avAsset adjustments:adjustments path:path watcher:watcher inhibitAudio:false entityRenderer:entityRenderer];
}
+ (SSignal *)convertAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher inhibitAudio:(bool)inhibitAudio entityRenderer:(id<TGPhotoPaintEntityRenderer>)entityRenderer
+ (SSignal *)convertAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments path:(NSString *)path watcher:(TGMediaVideoFileWatcher *)watcher inhibitAudio:(bool)inhibitAudio entityRenderer:(id<TGPhotoPaintEntityRenderer>)entityRenderer
{
if ([avAsset isKindOfClass:[NSURL class]]) {
avAsset = [[AVURLAsset alloc] initWithURL:(NSURL *)avAsset options:nil];
@ -116,7 +116,7 @@
return [[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
{
SAtomic *context = [[SAtomic alloc] initWithValue:[TGMediaVideoConversionContext contextWithQueue:queue subscriber:subscriber]];
NSURL *outputUrl = [self _randomTemporaryURL];
NSURL *outputUrl = [NSURL fileURLWithPath:path];
NSArray *requiredKeys = @[ @"tracks", @"duration" ];
[avAsset loadValuesAsynchronouslyForKeys:requiredKeys completionHandler:^
@ -222,14 +222,14 @@
}];
}
+ (SSignal *)renderUIImage:(UIImage *)image duration:(NSTimeInterval)duration adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher entityRenderer:(id<TGPhotoPaintEntityRenderer>)entityRenderer
+ (SSignal *)renderUIImage:(UIImage *)image duration:(NSTimeInterval)duration adjustments:(TGMediaVideoEditAdjustments *)adjustments path:(NSString *)path watcher:(TGMediaVideoFileWatcher *)watcher entityRenderer:(id<TGPhotoPaintEntityRenderer>)entityRenderer
{
SQueue *queue = [[SQueue alloc] init];
return [[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
{
SAtomic *context = [[SAtomic alloc] initWithValue:[TGMediaVideoConversionContext contextWithQueue:queue subscriber:subscriber]];
NSURL *outputUrl = [self _randomTemporaryURL];
NSURL *outputUrl = [NSURL fileURLWithPath:path];
NSString *path = TGComponentsPathForResource(@"blank", @"mp4");
AVAsset *avAsset = [[AVURLAsset alloc] initWithURL:[NSURL fileURLWithPath:path] options:nil];
@ -895,11 +895,6 @@
return CGSizeMake(renderWidth, renderHeight);
}
+ (NSURL *)_randomTemporaryURL
{
return [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSString alloc] initWithFormat:@"%x.mp4", (int)arc4random()]]];
}
+ (NSUInteger)estimatedSizeForPreset:(TGMediaVideoConversionPreset)preset duration:(NSTimeInterval)duration hasAudio:(bool)hasAudio
{
NSInteger bitrate = [TGMediaVideoConversionPresetSettings _videoBitrateKbpsForPreset:preset];

View File

@ -668,4 +668,32 @@ const CGFloat TGPhotoEditorSliderViewInternalMargin = 7.0f;
self.interactionEnded();
}
- (void)increase {
self.value = MIN(self.maximumValue, self.value + 1);
[self sendActionsForControlEvents:UIControlEventValueChanged];
[self setNeedsLayout];
}
- (void)increaseBy:(CGFloat)delta {
self.value = MIN(self.maximumValue, self.value + delta);
[self sendActionsForControlEvents:UIControlEventValueChanged];
[self setNeedsLayout];
}
- (void)decrease {
self.value = MAX(self.minimumValue, self.value - 1);
[self sendActionsForControlEvents:UIControlEventValueChanged];
[self setNeedsLayout];
}
- (void)decreaseBy:(CGFloat)delta {
self.value = MAX(self.minimumValue, self.value - delta);
[self sendActionsForControlEvents:UIControlEventValueChanged];
[self setNeedsLayout];
}
@end

View File

@ -648,6 +648,11 @@ const NSTimeInterval TGPhotoQualityPreviewDuration = 15.0f;
[(TGPhotoEditorController *)self.parentViewController setInfoString:fileSize];
}
+ (NSString *)_randomTemporaryPath
{
return [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSString alloc] initWithFormat:@"%x.mp4", (int)arc4random()]];
}
- (void)generateVideoPreview
{
if (self.preset == _currentPreset)
@ -665,6 +670,8 @@ const NSTimeInterval TGPhotoQualityPreviewDuration = 15.0f;
TGVideoEditAdjustments *adjustments = [self.photoEditor exportAdjustments];
adjustments = [adjustments editAdjustmentsWithPreset:self.preset maxDuration:TGPhotoQualityPreviewDuration];
NSString *path = [TGPhotoQualityController _randomTemporaryPath];
__block NSTimeInterval delay = 0.0;
__weak TGPhotoQualityController *weakSelf = self;
SSignal *convertSignal = [[assetSignal onNext:^(AVAsset *next) {
@ -680,7 +687,7 @@ const NSTimeInterval TGPhotoQualityPreviewDuration = 15.0f;
{
return [[[[[SSignal single:avAsset] delay:delay onQueue:[SQueue concurrentDefaultQueue]] mapToSignal:^SSignal *(AVAsset *avAsset)
{
return [TGMediaVideoConverter convertAVAsset:avAsset adjustments:adjustments watcher:nil inhibitAudio:true entityRenderer:nil];
return [TGMediaVideoConverter convertAVAsset:avAsset adjustments:adjustments path:path watcher:nil inhibitAudio:true entityRenderer:nil];
}] onError:^(__unused id error) {
delay = 1.0;
}] retryIf:^bool(__unused id error)

View File

@ -751,7 +751,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
case .assets:
signals = TGMediaAssetsController.resultSignals(for: controller.interaction?.selectionState, editingContext: controller.interaction?.editingState, intent: asFile ? TGMediaAssetsControllerSendFileIntent : TGMediaAssetsControllerSendMediaIntent, currentItem: nil, storeAssets: true, convertToJpeg: convertToJpeg, descriptionGenerator: legacyAssetPickerItemGenerator(), saveEditedPhotos: controller.saveEditedPhotos)
case .media:
signals = TGMediaAssetsController.pasteboardResultSignals(for: controller.interaction?.selectionState, editingContext: controller.interaction?.editingState, intent: asFile ? TGMediaAssetsControllerSendFileIntent : TGMediaAssetsControllerSendMediaIntent, currentItem: nil, descriptionGenerator: legacyAssetPickerItemGenerator())
signals = TGMediaAssetsController.pasteboardResultSignals(for: controller.interaction?.selectionState, editingContext: controller.interaction?.editingState, intent: asFile ? TGMediaAssetsControllerSendFileIntent : TGMediaAssetsControllerSendMediaIntent, currentItem: nil, descriptionGenerator: legacyAssetPickerItemGenerator())
}
guard let signals = signals else {
return

View File

@ -125,8 +125,12 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
}
self.cancelButtonNode.setTitle(strings.Common_Cancel, with: buttonFont, with: .white, for: .normal)
self.cancelButtonNode.accessibilityLabel = strings.Common_Cancel
self.cancelButtonNode.accessibilityTraits = [.button]
self.deleteButtonNode.setTitle(strings.Common_Delete, with: buttonFont, with: .white, for: .normal)
self.deleteButtonNode.accessibilityLabel = strings.Common_Delete
self.deleteButtonNode.accessibilityTraits = [.button]
if let biometricsType = self.biometricsType {
switch biometricsType {
case .touchId:

View File

@ -128,6 +128,9 @@ final class PasscodeEntryButtonNode: HighlightTrackingButtonNode {
super.init()
self.accessibilityLabel = title
self.accessibilityTraits = .keyboardKey
if let gradientBackgroundNode = self.gradientBackgroundNode {
self.addSubnode(gradientBackgroundNode)
}

View File

@ -232,15 +232,20 @@ public class ItemListCallListItemNode: ListViewItemNode {
insets = UIEdgeInsets()
}
var accessibilityText = ""
let earliestMessage = item.messages.sorted(by: {$0.timestamp < $1.timestamp}).first!
let titleText = stringForDate(timestamp: earliestMessage.timestamp, strings: item.presentationData.strings)
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: titleText, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - leftInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
accessibilityText.append(titleText)
accessibilityText.append(". ")
contentHeight += titleLayout.size.height + 18.0
var index = 0
var nodesLayout: [(TextNodeLayout, TextNodeLayout)] = []
var nodesApply: [(() -> TextNode, () -> TextNode)] = []
for message in item.messages {
let makeTimeLayout = makeNodesLayout[index].0
let time = stringForMessageTimestamp(timestamp: message.timestamp, dateTimeFormat: item.dateTimeFormat)
@ -250,6 +255,8 @@ public class ItemListCallListItemNode: ListViewItemNode {
let type = stringForCallType(message: message, strings: item.presentationData.strings)
let (typeLayout, typeApply) = makeTypeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: type, font: typeFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - leftInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
accessibilityText.append("\(time) - \(type)")
nodesLayout.append((timeLayout, typeLayout))
nodesApply.append((timeApply, typeApply))
@ -336,6 +343,8 @@ public class ItemListCallListItemNode: ListViewItemNode {
index += 1
}
strongSelf.accessibilityArea.accessibilityLabel = accessibilityText
strongSelf.accessibilityArea.accessibilityTraits = .staticText
strongSelf.accessibilityArea.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
}
})

View File

@ -55,7 +55,7 @@ final class HelloView: UIView, PhoneDemoDecorationView {
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupAnimations() {
guard self.activePhrases.isEmpty else {
return
@ -121,11 +121,13 @@ final class HelloView: UIView, PhoneDemoDecorationView {
self.activePositions.insert(positionIndex)
let duration: Double = Double.random(in: 1.75...2.25)
view.layer.animateKeyframes(values: [0.0, 1.0, 0.0] as [NSNumber], duration: duration, keyPath: "opacity", removeOnCompletion: false, completion: { [weak view] _ in
self.activePhrases.remove(index)
self.activePositions.remove(positionIndex)
view?.removeFromSuperview()
self.spawnNextPhrase()
view.layer.animateKeyframes(values: [0.0, 1.0, 0.0] as [NSNumber], duration: duration, keyPath: "opacity", removeOnCompletion: false, completion: { [weak view, weak self] _ in
if let self {
self.activePhrases.remove(index)
self.activePositions.remove(positionIndex)
view?.removeFromSuperview()
self.spawnNextPhrase()
}
})
view.layer.animateScale(from: CGFloat.random(in: 0.4 ..< 0.6), to: CGFloat.random(in: 0.9 ..< 1.2), duration: duration, removeOnCompletion: false)

View File

@ -81,7 +81,7 @@ public final class PageIndicatorComponent: Component {
private final class PageIndicatorView: UIView {
var displayCount: Int {
return min(11, self.pageCount)
return min(12, self.pageCount)
}
var dotSize: CGFloat = 8.0
var dotSpace: CGFloat = 10.0
@ -403,5 +403,3 @@ private class ItemView: UIView {
})
}
}

View File

@ -260,11 +260,13 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent {
let defaultPrice = product.storeProduct.defaultPrice(shortestOptionPrice.1, monthsCount: Int(product.months))
var subtitle = ""
var accessibilitySubtitle = ""
var pricePerMonth = product.storeProduct.pricePerMonth(Int(product.months))
pricePerMonth = environment.strings.Premium_PricePerMonth(pricePerMonth).string
if discountValue > 0 {
subtitle = "**\(defaultPrice)** \(product.price)"
accessibilitySubtitle = product.price
}
items.append(SectionGroupComponent.Item(
@ -285,6 +287,7 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent {
)
)
),
accessibilityLabel: "\(giftTitle). \(accessibilitySubtitle). \(pricePerMonth)",
action: {
component.selectProduct(product.id)
})
@ -358,6 +361,7 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent {
)
)
),
accessibilityLabel: "\(perk.title(strings: strings)). \(perk.subtitle(strings: strings))",
action: { [weak state] in
var demoSubject: PremiumDemoScreen.Subject
switch perk {

View File

@ -842,11 +842,13 @@ private final class CheckComponent: Component {
final class SectionGroupComponent: Component {
public final class Item: Equatable {
public let content: AnyComponentWithIdentity<Empty>
public let accessibilityLabel: String
public let isEnabled: Bool
public let action: () -> Void
public init(_ content: AnyComponentWithIdentity<Empty>, isEnabled: Bool = true, action: @escaping () -> Void) {
public init(_ content: AnyComponentWithIdentity<Empty>, accessibilityLabel: String, isEnabled: Bool = true, action: @escaping () -> Void) {
self.content = content
self.accessibilityLabel = accessibilityLabel
self.isEnabled = isEnabled
self.action = action
}
@ -855,6 +857,9 @@ final class SectionGroupComponent: Component {
if lhs.content != rhs.content {
return false
}
if lhs.accessibilityLabel != rhs.accessibilityLabel {
return false
}
if lhs.isEnabled != rhs.isEnabled {
return false
}
@ -947,6 +952,7 @@ final class SectionGroupComponent: Component {
self.buttonViews[item.content.id] = buttonView
self.addSubview(buttonView)
}
buttonView.accessibilityLabel = item.accessibilityLabel
if let current = self.itemViews[item.content.id] {
itemView = current
@ -1105,7 +1111,8 @@ final class PerkComponent: CombinedComponent {
colors: component.iconBackgroundColors,
cornerRadius: 7.0,
gradientDirection: .vertical),
availableSize: iconSize, transition: context.transition
availableSize: iconSize,
transition: context.transition
)
let icon = icon.update(
@ -1179,7 +1186,7 @@ final class PerkComponent: CombinedComponent {
context.add(arrow
.position(CGPoint(x: context.availableSize.width - 7.0 - arrow.size.width / 2.0, y: size.height / 2.0))
)
return size
}
}
@ -1196,14 +1203,28 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
let otherPeerName: String?
let products: [PremiumProduct]?
let selectedProductId: String?
let validTransactionIds: [String]
let validPurchases: [InAppPurchaseManager.ReceiptPurchase]
let promoConfiguration: PremiumPromoConfiguration?
let present: (ViewController) -> Void
let selectProduct: (String) -> Void
let buy: () -> Void
let updateIsFocused: (Bool) -> Void
init(context: AccountContext, source: PremiumSource, isPremium: Bool?, justBought: Bool, otherPeerName: String?, products: [PremiumProduct]?, selectedProductId: String?, validTransactionIds: [String], promoConfiguration: PremiumPromoConfiguration?, present: @escaping (ViewController) -> Void, selectProduct: @escaping (String) -> Void, buy: @escaping () -> Void, updateIsFocused: @escaping (Bool) -> Void) {
init(
context: AccountContext,
source: PremiumSource,
isPremium: Bool?,
justBought: Bool,
otherPeerName: String?,
products: [PremiumProduct]?,
selectedProductId: String?,
validPurchases: [InAppPurchaseManager.ReceiptPurchase],
promoConfiguration: PremiumPromoConfiguration?,
present: @escaping (ViewController) -> Void,
selectProduct: @escaping (String) -> Void,
buy: @escaping () -> Void,
updateIsFocused: @escaping (Bool) -> Void
) {
self.context = context
self.source = source
self.isPremium = isPremium
@ -1211,7 +1232,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
self.otherPeerName = otherPeerName
self.products = products
self.selectedProductId = selectedProductId
self.validTransactionIds = validTransactionIds
self.validPurchases = validPurchases
self.promoConfiguration = promoConfiguration
self.present = present
self.selectProduct = selectProduct
@ -1241,7 +1262,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
if lhs.selectedProductId != rhs.selectedProductId {
return false
}
if lhs.validTransactionIds != rhs.validTransactionIds {
if lhs.validPurchases != rhs.validPurchases {
return false
}
if lhs.promoConfiguration != rhs.promoConfiguration {
@ -1256,7 +1277,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
var products: [PremiumProduct]?
var selectedProductId: String?
var validTransactionIds: [String] = []
var validPurchases: [InAppPurchaseManager.ReceiptPurchase] = []
var isPremium: Bool?
@ -1276,7 +1297,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
var canUpgrade: Bool {
if let products = self.products, let current = products.first(where: { $0.isCurrent }), let transactionId = current.transactionId {
if self.validTransactionIds.contains(transactionId) {
if self.validPurchases.contains(where: { $0.transactionId == transactionId }) {
return products.first(where: { $0.months > current.months }) != nil
} else {
return false
@ -1373,7 +1394,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
let state = context.state
state.products = context.component.products
state.selectedProductId = context.component.selectedProductId
state.validTransactionIds = context.component.validTransactionIds
state.validPurchases = context.component.validPurchases
state.isPremium = context.component.isPremium
let theme = environment.theme
@ -1543,14 +1564,17 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
let defaultPrice = product.storeProduct.defaultPrice(shortestOptionPrice.1, monthsCount: Int(product.months))
var subtitle = ""
var accessibilitySubtitle = ""
var pricePerMonth = product.price
if product.months > 1 {
pricePerMonth = product.storeProduct.pricePerMonth(Int(product.months))
if discountValue > 0 {
subtitle = "**\(defaultPrice)** \(product.price)"
accessibilitySubtitle = product.price
if product.months == 12 {
subtitle = environment.strings.Premium_PricePerYear(subtitle).string
accessibilitySubtitle = environment.strings.Premium_PricePerYear(accessibilitySubtitle).string
}
} else {
subtitle = product.price
@ -1558,6 +1582,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
}
if product.isCurrent {
subtitle = environment.strings.Premium_CurrentPlan
accessibilitySubtitle = subtitle
}
pricePerMonth = environment.strings.Premium_PricePerMonth(pricePerMonth).string
@ -1580,6 +1605,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
)
)
),
accessibilityLabel: "\(giftTitle). \(accessibilitySubtitle). \(pricePerMonth)",
isEnabled: product.months > currentProductMonths,
action: {
selectProduct(product.id)
@ -1637,6 +1663,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
)
)
),
accessibilityLabel: "\(perk.title(strings: strings)). \(perk.subtitle(strings: strings))",
action: { [weak state] in
var demoSubject: PremiumDemoScreen.Subject
switch perk {
@ -1986,7 +2013,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
private(set) var products: [PremiumProduct]?
private(set) var selectedProductId: String?
fileprivate var validTransactionIds: [String] = []
fileprivate var validPurchases: [InAppPurchaseManager.ReceiptPurchase] = []
var isPremium: Bool?
var otherPeerName: String?
@ -2015,7 +2042,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
var canUpgrade: Bool {
if let products = self.products, let current = products.first(where: { $0.isCurrent }), let transactionId = current.transactionId {
if self.validTransactionIds.contains(transactionId) {
if self.validPurchases.contains(where: { $0.transactionId == transactionId }) {
return products.first(where: { $0.months > current.months }) != nil
} else {
return false
@ -2036,7 +2063,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
super.init()
self.validTransactionIds = context.inAppPurchaseManager?.getValidTransactionIds() ?? []
self.validPurchases = context.inAppPurchaseManager?.getReceiptPurchases() ?? []
let availableProducts: Signal<[InAppPurchaseManager.Product], NoError>
if let inAppPurchaseManager = context.inAppPurchaseManager {
@ -2136,7 +2163,28 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
let premiumProduct = self.products?.first(where: { $0.id == self.selectedProductId }), !self.inProgress else {
return
}
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let isUpgrade = self.products?.first(where: { $0.isCurrent }) != nil
var hasActiveSubsciption = false
if let data = self.context.currentAppConfiguration.with({ $0 }).data, let _ = data["ios_killswitch_disable_receipt_check"] {
} else if !self.validPurchases.isEmpty && !isUpgrade {
let now = Date()
for purchase in self.validPurchases.reversed() {
if (purchase.productId.hasSuffix(".monthly") || purchase.productId.hasSuffix(".annual")) && purchase.expirationDate > now {
hasActiveSubsciption = true
}
}
}
if hasActiveSubsciption {
let errorText = presentationData.strings.Premium_Purchase_OnlyOneSubscriptionAllowed
let alertController = textAlertController(context: self.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
self.present(alertController)
return
}
addAppLogEvent(postbox: self.context.account.postbox, type: "premium.promo_screen_accept")
@ -2173,7 +2221,6 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
addAppLogEvent(postbox: strongSelf.context.account.postbox, type: "premium.promo_screen_fail")
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
let alertController = textAlertController(context: strongSelf.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
strongSelf.present(alertController)
@ -2198,7 +2245,6 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
strongSelf.updateInProgress(false)
strongSelf.updated(transition: .immediate)
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
var errorText: String?
switch error {
case .generic:
@ -2475,7 +2521,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
otherPeerName: state.otherPeerName,
products: state.products,
selectedProductId: state.selectedProductId,
validTransactionIds: state.validTransactionIds,
validPurchases: state.validPurchases,
promoConfiguration: state.promoConfiguration,
present: context.component.present,
selectProduct: { [weak state] productId in

View File

@ -836,7 +836,7 @@ public class PremiumLimitsListScreen: ViewController {
} else {
topInset = max(0.0, panInitialTopInset + min(0.0, panOffset))
}
} else if let dismissOffset = self.dismissOffset {
} else if let dismissOffset = self.dismissOffset, !dismissOffset.isZero {
topInset = edgeTopInset * dismissOffset
} else {
topInset = effectiveExpanded ? 0.0 : edgeTopInset

View File

@ -254,11 +254,13 @@ typedef enum {
_glkView.transform = CGAffineTransformIdentity;
} completion:nil];
_glkView.alpha = 0.0;
_pageScrollView.alpha = 0.0;
_pageControl.alpha = 0.0;
_startButton.alpha = 0.0;
[UIView animateWithDuration:0.3 delay:0.15 options:kNilOptions animations:^{
_glkView.alpha = 1.0;
_pageScrollView.alpha = 1.0;
_pageControl.alpha = 1.0;
_startButton.alpha = 1.0;

View File

@ -1067,6 +1067,8 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
node.isHidden = true
self.textField.isUserInteractionEnabled = false
if !self.clearButton.isHidden {
let xOffset = targetTextBackgroundFrame.width - self.textBackgroundNode.frame.width
if !xOffset.isZero {
@ -1118,10 +1120,11 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
var backgroundCompleted = false
var separatorCompleted = false
var textBackgroundCompleted = false
let intermediateCompletion: () -> Void = { [weak node] in
let intermediateCompletion: () -> Void = { [weak node, weak self] in
if backgroundCompleted && separatorCompleted && textBackgroundCompleted {
completion()
node?.isHidden = false
self?.textField.isUserInteractionEnabled = true
}
}

View File

@ -429,6 +429,9 @@ private final class BubbleSettingsToolbarNode: ASDisplayNode {
super.init()
self.cancelButton.accessibilityTraits = [.button]
self.doneButton.accessibilityTraits = [.button]
self.addSubnode(self.switchItemNode)
self.addSubnode(self.cornerRadiusItemNode)
self.addSubnode(self.cancelButton)
@ -482,6 +485,9 @@ private final class BubbleSettingsToolbarNode: ASDisplayNode {
self.cancelButton.setTitle(presentationData.strings.Common_Cancel, with: Font.regular(17.0), with: presentationData.theme.list.itemPrimaryTextColor, for: [])
self.doneButton.setTitle(presentationData.strings.Wallpaper_Set, with: Font.regular(17.0), with: presentationData.theme.list.itemPrimaryTextColor, for: [])
self.cancelButton.accessibilityLabel = presentationData.strings.Common_Cancel
self.doneButton.accessibilityLabel = presentationData.strings.Wallpaper_Set
}
func updatePresentationThemeSettings(presentationThemeSettings: PresentationThemeSettings) {

View File

@ -95,6 +95,8 @@ private final class AutodownloadDataUsagePickerItemNode: ListViewItemNode {
private let customTextNode: TextNode
private var sliderView: TGPhotoEditorSliderView?
private let activateArea: AccessibilityAreaNode
private var item: AutodownloadDataUsagePickerItem?
private var layoutParams: ListViewItemLayoutParams?
@ -126,12 +128,27 @@ private final class AutodownloadDataUsagePickerItemNode: ListViewItemNode {
self.customTextNode.isUserInteractionEnabled = false
self.customTextNode.displaysAsynchronously = false
self.activateArea = AccessibilityAreaNode()
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.lowTextNode)
self.addSubnode(self.mediumTextNode)
self.addSubnode(self.highTextNode)
self.addSubnode(self.customTextNode)
self.addSubnode(self.activateArea)
self.activateArea.increment = { [weak self] in
if let self {
self.sliderView?.increase()
}
}
self.activateArea.decrement = { [weak self] in
if let self {
self.sliderView?.decrease()
}
}
}
func updateSliderView() {
@ -154,6 +171,8 @@ private final class AutodownloadDataUsagePickerItemNode: ListViewItemNode {
sliderView.isUserInteractionEnabled = item.enabled
sliderView.alpha = item.enabled ? 1.0 : 0.4
sliderView.layer.allowsGroupOpacity = !item.enabled
self.updateAccessibilityLabels()
}
}
@ -323,11 +342,33 @@ private final class AutodownloadDataUsagePickerItemNode: ListViewItemNode {
strongSelf.updateSliderView()
}
strongSelf.activateArea.accessibilityLabel = item.strings.AutoDownloadSettings_DataUsage
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))
}
})
}
}
private func updateAccessibilityLabels() {
guard let item = self.item else {
return
}
var textNodes: [TextNode] = [self.lowTextNode, self.mediumTextNode, self.highTextNode]
if let customPosition = item.customPosition {
textNodes.insert(self.customTextNode, at: customPosition)
}
if let value = self.sliderView?.value {
self.activateArea.accessibilityValue = textNodes[Int(value)].cachedLayout?.attributedString?.string ?? ""
}
var accessibilityTraits: UIAccessibilityTraits = [.adjustable]
if item.enabled {
} else {
accessibilityTraits.insert(.notEnabled)
}
self.activateArea.accessibilityTraits = accessibilityTraits
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
}

View File

@ -30,6 +30,7 @@ private final class DataAndStorageControllerArguments {
let openSaveIncoming: (AutomaticSaveIncomingPeerType) -> Void
let toggleSaveEditedPhotos: (Bool) -> Void
let togglePauseMusicOnRecording: (Bool) -> Void
let toggleRaiseToListen: (Bool) -> Void
let toggleAutoplayGifs: (Bool) -> Void
let toggleAutoplayVideos: (Bool) -> Void
let toggleDownloadInBackground: (Bool) -> Void
@ -37,7 +38,7 @@ private final class DataAndStorageControllerArguments {
let openIntents: () -> Void
let toggleEnableSensitiveContent: (Bool) -> Void
init(openStorageUsage: @escaping () -> Void, openNetworkUsage: @escaping () -> Void, openProxy: @escaping () -> Void, openAutomaticDownloadConnectionType: @escaping (AutomaticDownloadConnectionType) -> Void, resetAutomaticDownload: @escaping () -> Void, toggleVoiceUseLessData: @escaping (Bool) -> Void, openSaveIncoming: @escaping (AutomaticSaveIncomingPeerType) -> Void, toggleSaveEditedPhotos: @escaping (Bool) -> Void, togglePauseMusicOnRecording: @escaping (Bool) -> Void, toggleAutoplayGifs: @escaping (Bool) -> Void, toggleAutoplayVideos: @escaping (Bool) -> Void, toggleDownloadInBackground: @escaping (Bool) -> Void, openBrowserSelection: @escaping () -> Void, openIntents: @escaping () -> Void, toggleEnableSensitiveContent: @escaping (Bool) -> Void) {
init(openStorageUsage: @escaping () -> Void, openNetworkUsage: @escaping () -> Void, openProxy: @escaping () -> Void, openAutomaticDownloadConnectionType: @escaping (AutomaticDownloadConnectionType) -> Void, resetAutomaticDownload: @escaping () -> Void, toggleVoiceUseLessData: @escaping (Bool) -> Void, openSaveIncoming: @escaping (AutomaticSaveIncomingPeerType) -> Void, toggleSaveEditedPhotos: @escaping (Bool) -> Void, togglePauseMusicOnRecording: @escaping (Bool) -> Void, toggleRaiseToListen: @escaping (Bool) -> Void, toggleAutoplayGifs: @escaping (Bool) -> Void, toggleAutoplayVideos: @escaping (Bool) -> Void, toggleDownloadInBackground: @escaping (Bool) -> Void, openBrowserSelection: @escaping () -> Void, openIntents: @escaping () -> Void, toggleEnableSensitiveContent: @escaping (Bool) -> Void) {
self.openStorageUsage = openStorageUsage
self.openNetworkUsage = openNetworkUsage
self.openProxy = openProxy
@ -47,6 +48,7 @@ private final class DataAndStorageControllerArguments {
self.openSaveIncoming = openSaveIncoming
self.toggleSaveEditedPhotos = toggleSaveEditedPhotos
self.togglePauseMusicOnRecording = togglePauseMusicOnRecording
self.toggleRaiseToListen = toggleRaiseToListen
self.toggleAutoplayGifs = toggleAutoplayGifs
self.toggleAutoplayVideos = toggleAutoplayVideos
self.toggleDownloadInBackground = toggleDownloadInBackground
@ -74,6 +76,8 @@ public enum DataAndStorageEntryTag: ItemListItemTag, Equatable {
case autoplayVideos
case saveEditedPhotos
case downloadInBackground
case pauseMusicOnRecording
case raiseToListen
case autoSave(AutomaticSaveIncomingPeerType)
public func isEqual(to other: ItemListItemTag) -> Bool {
@ -108,9 +112,11 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
case otherHeader(PresentationTheme, String)
case shareSheet(PresentationTheme, String)
case saveEditedPhotos(PresentationTheme, String, Bool)
case pauseMusicOnRecording(PresentationTheme, String, Bool)
case openLinksIn(PresentationTheme, String, String)
case pauseMusicOnRecording(PresentationTheme, String, Bool)
case raiseToListen(PresentationTheme, String, Bool)
case raiseToListenInfo(PresentationTheme, String)
case connectionHeader(PresentationTheme, String)
case connectionProxy(PresentationTheme, String, String)
case enableSensitiveContent(String, Bool)
@ -129,7 +135,7 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
return DataAndStorageSection.voiceCalls.rawValue
case .autoplayHeader, .autoplayGifs, .autoplayVideos:
return DataAndStorageSection.autoPlay.rawValue
case .otherHeader, .shareSheet, .saveEditedPhotos, .pauseMusicOnRecording, .openLinksIn:
case .otherHeader, .shareSheet, .saveEditedPhotos, .openLinksIn, .pauseMusicOnRecording, .raiseToListen, .raiseToListenInfo:
return DataAndStorageSection.other.rawValue
case .connectionHeader, .connectionProxy:
return DataAndStorageSection.connection.rawValue
@ -152,14 +158,12 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
return 4
case .automaticDownloadReset:
return 5
case .autoSaveHeader:
return 6
case let .autoSaveItem(index, _, _, _, _):
return 7 + Int32(index)
case .autoSaveInfo:
return 20
case .downloadInBackground:
return 21
case .downloadInBackgroundInfo:
@ -180,16 +184,20 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
return 29
case .saveEditedPhotos:
return 31
case .pauseMusicOnRecording:
return 32
case .openLinksIn:
return 32
case .pauseMusicOnRecording:
return 33
case .connectionHeader:
case .raiseToListen:
return 34
case .connectionProxy:
case .raiseToListenInfo:
return 35
case .enableSensitiveContent:
case .connectionHeader:
return 36
case .connectionProxy:
return 37
case .enableSensitiveContent:
return 38
}
}
@ -297,14 +305,26 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
} else {
return false
}
case let .openLinksIn(lhsTheme, lhsText, lhsValue):
if case let .openLinksIn(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .pauseMusicOnRecording(lhsTheme, lhsText, lhsValue):
if case let .pauseMusicOnRecording(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .openLinksIn(lhsTheme, lhsText, lhsValue):
if case let .openLinksIn(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
case let .raiseToListen(lhsTheme, lhsText, lhsValue):
if case let .raiseToListen(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .raiseToListenInfo(lhsTheme, lhsText):
if case let .raiseToListenInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
@ -420,14 +440,20 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.toggleSaveEditedPhotos(value)
}, tag: DataAndStorageEntryTag.saveEditedPhotos)
case let .pauseMusicOnRecording(_, text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.togglePauseMusicOnRecording(value)
}, tag: DataAndStorageEntryTag.saveEditedPhotos)
case let .openLinksIn(_, text, value):
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: {
arguments.openBrowserSelection()
})
case let .pauseMusicOnRecording(_, text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.togglePauseMusicOnRecording(value)
}, tag: DataAndStorageEntryTag.pauseMusicOnRecording)
case let .raiseToListen(_, text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.toggleRaiseToListen(value)
}, tag: DataAndStorageEntryTag.raiseToListen)
case let .raiseToListenInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
case let .downloadInBackground(_, text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.toggleDownloadInBackground(value)
@ -646,8 +672,10 @@ private func dataAndStorageControllerEntries(state: DataAndStorageControllerStat
entries.append(.shareSheet(presentationData.theme, presentationData.strings.ChatSettings_IntentsSettings))
}
entries.append(.saveEditedPhotos(presentationData.theme, presentationData.strings.Settings_SaveEditedPhotos, data.generatedMediaStoreSettings.storeEditedPhotos))
entries.append(.pauseMusicOnRecording(presentationData.theme, presentationData.strings.Settings_PauseMusicOnRecording, data.mediaInputSettings.pauseMusicOnRecording))
entries.append(.openLinksIn(presentationData.theme, presentationData.strings.ChatSettings_OpenLinksIn, defaultWebBrowser))
entries.append(.pauseMusicOnRecording(presentationData.theme, presentationData.strings.Settings_PauseMusicOnRecording, data.mediaInputSettings.pauseMusicOnRecording))
entries.append(.raiseToListen(presentationData.theme, presentationData.strings.Settings_RaiseToListen, data.mediaInputSettings.enableRaiseToSpeak))
entries.append(.raiseToListenInfo(presentationData.theme, presentationData.strings.Settings_RaiseToListenInfo))
let proxyValue: String
if let proxySettings = data.proxySettings, let activeServer = proxySettings.activeServer, proxySettings.enabled {
@ -878,6 +906,10 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da
let _ = updateMediaInputSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
return current.withUpdatedPauseMusicOnRecording(value)
}).start()
}, toggleRaiseToListen: { value in
let _ = updateMediaInputSettingsInteractively(accountManager: context.sharedContext.accountManager, {
$0.withUpdatedEnableRaiseToSpeak(value)
}).start()
}, toggleAutoplayGifs: { value in
let _ = updateMediaDownloadSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings in
var settings = settings

View File

@ -135,6 +135,8 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
return self.containerNode
}
private let activateArea: AccessibilityAreaNode
private var layoutParams: (ItemListWebsiteItem, ListViewItemLayoutParams, ItemListNeighbors)?
private var editableControlNode: ItemListEditableControlNode?
@ -184,6 +186,8 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isLayerBacked = true
self.activateArea = AccessibilityAreaNode()
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
self.addSubnode(self.containerNode)
@ -191,6 +195,8 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
self.containerNode.addSubnode(self.titleNode)
self.containerNode.addSubnode(self.appNode)
self.containerNode.addSubnode(self.locationNode)
self.addSubnode(self.activateArea)
}
func asyncLayout() -> (_ item: ItemListWebsiteItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) {
@ -288,6 +294,28 @@ class ItemListWebsiteItemNode: 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))
strongSelf.activateArea.accessibilityLabel = titleAttributedString?.string ?? ""
var value = ""
if let string = appAttributedString?.string {
value += string
}
if let string = locationAttributedString?.string {
if !value.isEmpty {
value += "\n"
}
value += string
}
strongSelf.activateArea.accessibilityValue = value
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

@ -716,6 +716,9 @@ private final class TextSelectionToolbarNode: ASDisplayNode {
super.init()
self.cancelButton.accessibilityTraits = [.button]
self.doneButton.accessibilityTraits = [.button]
self.addSubnode(self.switchItemNode)
self.addSubnode(self.fontSizeItemNode)
self.addSubnode(self.cancelButton)
@ -769,6 +772,9 @@ private final class TextSelectionToolbarNode: ASDisplayNode {
self.cancelButton.setTitle(presentationData.strings.Common_Cancel, with: Font.regular(17.0), with: presentationData.theme.list.itemPrimaryTextColor, for: [])
self.doneButton.setTitle(presentationData.strings.Wallpaper_Set, with: Font.regular(17.0), with: presentationData.theme.list.itemPrimaryTextColor, for: [])
self.cancelButton.accessibilityLabel = presentationData.strings.Common_Cancel
self.doneButton.accessibilityLabel = presentationData.strings.Wallpaper_Set
}
func updatePresentationThemeSettings(presentationThemeSettings: PresentationThemeSettings) {

View File

@ -203,6 +203,8 @@ private final class ThemeCarouselThemeItemIconNode : ListViewItemNode {
private var placeholderNode: StickerShimmerEffectNode
var snapshotView: UIView?
private let activateAreaNode: AccessibilityAreaNode
var item: ThemeCarouselThemeIconItem?
override var visibility: ListViewItemNodeVisibility {
@ -246,6 +248,8 @@ private final class ThemeCarouselThemeItemIconNode : ListViewItemNode {
self.emojiImageNode = TransformImageNode()
self.placeholderNode = StickerShimmerEffectNode()
self.activateAreaNode = AccessibilityAreaNode()
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
@ -272,6 +276,8 @@ private final class ThemeCarouselThemeItemIconNode : ListViewItemNode {
}
firstTime = false
}
self.addSubnode(self.activateAreaNode)
}
deinit {
@ -442,6 +448,16 @@ private final class ThemeCarouselThemeItemIconNode : ListViewItemNode {
animatedStickerNode.frame = emojiFrame
animatedStickerNode.updateLayout(size: emojiFrame.size)
}
let presentationData = item.context.sharedContext.currentPresentationData.with { $0 }
strongSelf.activateAreaNode.accessibilityLabel = item.themeReference.emoticon.flatMap { presentationData.strings.Appearance_VoiceOver_Theme($0).string }
if item.selected {
strongSelf.activateAreaNode.accessibilityTraits = [.button, .selected]
} else {
strongSelf.activateAreaNode.accessibilityTraits = [.button]
}
strongSelf.activateAreaNode.frame = CGRect(origin: .zero, size: itemLayout.size)
}
})
}

View File

@ -102,6 +102,8 @@ private final class ThemeSettingsAppIconNode : ASDisplayNode {
private let textNode: ImmediateTextNode
private var action: (() -> Void)?
private let activateAreaNode: AccessibilityAreaNode
private var locked = false
override init() {
@ -122,12 +124,16 @@ private final class ThemeSettingsAppIconNode : ASDisplayNode {
self.textNode.isUserInteractionEnabled = false
self.textNode.displaysAsynchronously = false
self.activateAreaNode = AccessibilityAreaNode()
self.activateAreaNode.accessibilityTraits = [.button]
super.init()
self.addSubnode(self.iconNode)
self.addSubnode(self.overlayNode)
self.addSubnode(self.textNode)
self.addSubnode(self.lockNode)
self.addSubnode(self.activateAreaNode)
}
func setup(theme: PresentationTheme, icon: UIImage, title: NSAttributedString, locked: Bool, color: UIColor, bordered: Bool, selected: Bool, action: @escaping () -> Void) {
@ -140,6 +146,13 @@ private final class ThemeSettingsAppIconNode : ASDisplayNode {
action()
}
self.activateAreaNode.accessibilityLabel = title.string
if locked {
self.activateAreaNode.accessibilityTraits = [.button, .notEnabled]
} else {
self.activateAreaNode.accessibilityTraits = [.button]
}
self.setNeedsLayout()
}
@ -171,6 +184,8 @@ private final class ThemeSettingsAppIconNode : ASDisplayNode {
self.textNode.frame = textFrame
self.lockNode.frame = CGRect(x: self.textNode.frame.minX - 10.0, y: 90.0, width: 6.0, height: 8.0)
self.activateAreaNode.frame = self.bounds
}
}
@ -186,7 +201,7 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
private let scrollNode: ASScrollNode
private var nodes: [ThemeSettingsAppIconNode] = []
private var item: ThemeSettingsAppIconItem?
private var layoutParams: ListViewItemLayoutParams?

View File

@ -207,6 +207,8 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
private let titleNode: TextNode
var snapshotView: UIView?
private let activateAreaNode: AccessibilityAreaNode
var item: ThemeSettingsThemeIconItem?
init() {
@ -222,13 +224,17 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false
self.activateAreaNode = AccessibilityAreaNode()
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
self.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.imageNode)
self.containerNode.addSubnode(self.overlayNode)
self.containerNode.addSubnode(self.titleNode)
self.addSubnode(self.activateAreaNode)
self.containerNode.activated = { [weak self] gesture, _ in
guard let strongSelf = self, let item = strongSelf.item else {
@ -308,6 +314,15 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
strongSelf.overlayNode.frame = CGRect(origin: CGPoint(x: 9.0, y: 13.0), size: CGSize(width: 100.0, height: 64.0))
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 88.0), size: CGSize(width: itemLayout.contentSize.width, height: 16.0))
strongSelf.activateAreaNode.accessibilityLabel = item.title
if item.selected {
strongSelf.activateAreaNode.accessibilityTraits = [.button, .selected]
} else {
strongSelf.activateAreaNode.accessibilityTraits = [.button]
}
strongSelf.activateAreaNode.frame = CGRect(origin: .zero, size: itemLayout.size)
}
})
}

View File

@ -101,11 +101,13 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
if self.isEnabled != oldValue {
self.updateColors(animated: true)
}
self.updateAccessibilityLabels()
}
}
public var title: String? {
didSet {
self.updateAccessibilityLabels()
if let width = self.validLayout {
_ = self.updateLayout(width: width, transition: .immediate)
}
@ -114,6 +116,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
public var subtitle: String? {
didSet {
self.updateAccessibilityLabels()
if let width = self.validLayout {
_ = self.updateLayout(width: width, previousSubtitle: oldValue, transition: .immediate)
}
@ -126,6 +129,15 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
}
}
private func updateAccessibilityLabels() {
self.accessibilityLabel = (self.title ?? "") + " " + (self.subtitle ?? "")
if !self.isEnabled {
self.accessibilityTraits = [.button, .notEnabled]
} else {
self.accessibilityTraits = [.button]
}
}
private var animationTimer: SwiftSignalKit.Timer?
public var animation: String? {
didSet {
@ -245,6 +257,8 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
super.init()
self.isAccessibilityElement = true
self.addSubnode(self.buttonBackgroundNode)
self.addSubnode(self.buttonNode)
self.addSubnode(self.titleNode)
@ -281,6 +295,8 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
}
}
}
self.updateAccessibilityLabels()
}
public override func didLoad() {
@ -759,6 +775,7 @@ public final class SolidRoundedButtonView: UIView {
public var title: String? {
didSet {
self.updateAccessibilityLabels()
if let width = self.validLayout {
_ = self.updateLayout(width: width, transition: .immediate)
}
@ -767,6 +784,7 @@ public final class SolidRoundedButtonView: UIView {
public var label: String? {
didSet {
self.updateAccessibilityLabels()
if let width = self.validLayout {
_ = self.updateLayout(width: width, transition: .immediate)
}
@ -775,12 +793,23 @@ public final class SolidRoundedButtonView: UIView {
public var subtitle: String? {
didSet {
self.updateAccessibilityLabels()
if let width = self.validLayout {
_ = self.updateLayout(width: width, previousSubtitle: oldValue, transition: .immediate)
}
}
}
private func updateAccessibilityLabels() {
self.accessibilityLabel = (self.title ?? "") + " " + (self.subtitle ?? "")
self.accessibilityValue = self.label
if !self.isEnabled {
self.accessibilityTraits = [.button, .notEnabled]
} else {
self.accessibilityTraits = [.button]
}
}
public var icon: UIImage? {
didSet {
self.iconNode.image = generateTintedImage(image: self.icon, color: self.theme.foregroundColor)
@ -792,6 +821,7 @@ public final class SolidRoundedButtonView: UIView {
if self.isEnabled != oldValue {
self.titleNode.alpha = self.isEnabled ? 1.0 : 0.6
}
self.updateAccessibilityLabels()
}
}
@ -917,6 +947,8 @@ public final class SolidRoundedButtonView: UIView {
super.init(frame: CGRect())
self.isAccessibilityElement = true
self.addSubview(self.buttonBackgroundNode)
self.addSubview(self.buttonNode)
self.addSubview(self.titleNode)
@ -958,6 +990,8 @@ public final class SolidRoundedButtonView: UIView {
if #available(iOS 13.0, *) {
self.buttonBackgroundNode.layer.cornerCurve = .continuous
}
self.updateAccessibilityLabels()
}
required public init(coder: NSCoder) {

View File

@ -10,6 +10,7 @@ private extension ToolbarTheme {
}
final class TabBarControllerNode: ASDisplayNode {
private var navigationBarPresentationData: NavigationBarPresentationData
private var theme: TabBarControllerTheme
let tabBarNode: TabBarNode
private let disabledOverlayNode: ASDisplayNode
@ -27,8 +28,9 @@ final class TabBarControllerNode: ASDisplayNode {
}
}
init(theme: TabBarControllerTheme, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void, swipeAction: @escaping (Int, TabBarItemSwipeDirection) -> Void, toolbarActionSelected: @escaping (ToolbarActionOption) -> Void, disabledPressed: @escaping () -> Void) {
init(theme: TabBarControllerTheme, navigationBarPresentationData: NavigationBarPresentationData, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void, swipeAction: @escaping (Int, TabBarItemSwipeDirection) -> Void, toolbarActionSelected: @escaping (ToolbarActionOption) -> Void, disabledPressed: @escaping () -> Void) {
self.theme = theme
self.navigationBarPresentationData = navigationBarPresentationData
self.tabBarNode = TabBarNode(theme: theme, itemSelected: itemSelected, contextAction: contextAction, swipeAction: swipeAction)
self.disabledOverlayNode = ASDisplayNode()
self.disabledOverlayNode.backgroundColor = theme.backgroundColor.withAlphaComponent(0.5)
@ -60,8 +62,9 @@ final class TabBarControllerNode: ASDisplayNode {
}
}
func updateTheme(_ theme: TabBarControllerTheme) {
func updateTheme(_ theme: TabBarControllerTheme, navigationBarPresentationData: NavigationBarPresentationData) {
self.theme = theme
self.navigationBarPresentationData = navigationBarPresentationData
self.backgroundColor = theme.backgroundColor
self.tabBarNode.updateTheme(theme)

View File

@ -127,9 +127,11 @@ open class TabBarControllerImpl: ViewController, TabBarController {
private let pendingControllerDisposable = MetaDisposable()
private var navigationBarPresentationData: NavigationBarPresentationData
private var theme: TabBarControllerTheme
public init(navigationBarPresentationData: NavigationBarPresentationData, theme: TabBarControllerTheme) {
self.navigationBarPresentationData = navigationBarPresentationData
self.theme = theme
super.init(navigationBarPresentationData: nil)
@ -155,8 +157,9 @@ open class TabBarControllerImpl: ViewController, TabBarController {
public func updateTheme(navigationBarPresentationData: NavigationBarPresentationData, theme: TabBarControllerTheme) {
if self.theme !== theme {
self.theme = theme
self.navigationBarPresentationData = navigationBarPresentationData
if self.isNodeLoaded {
self.tabBarControllerNode.updateTheme(theme)
self.tabBarControllerNode.updateTheme(theme, navigationBarPresentationData: navigationBarPresentationData)
}
}
}
@ -194,7 +197,7 @@ open class TabBarControllerImpl: ViewController, TabBarController {
}
override open func loadDisplayNode() {
self.displayNode = TabBarControllerNode(theme: self.theme, itemSelected: { [weak self] index, longTap, itemNodes in
self.displayNode = TabBarControllerNode(theme: self.theme, navigationBarPresentationData: self.navigationBarPresentationData, itemSelected: { [weak self] index, longTap, itemNodes in
if let strongSelf = self {
if longTap, let controller = strongSelf.controllers[index] as? TabBarContainedController {
controller.presentTabBarPreviewingController(sourceNodes: itemNodes)

View File

@ -374,6 +374,7 @@ class TabBarNode: ASDisplayNode {
super.init()
self.isAccessibilityContainer = false
self.accessibilityTraits = [.tabBar]
self.isOpaque = false
self.backgroundColor = nil
@ -502,6 +503,7 @@ class TabBarNode: ASDisplayNode {
node.contextTextImageNode.image = contextTextImage
node.contextImageNode.image = contextImage
node.accessibilityLabel = item.item.title
node.accessibilityTraits = [.button, .selected]
node.contentWidth = max(contentWidth, imageContentWidth)
node.isSelected = true
} else {
@ -515,6 +517,7 @@ class TabBarNode: ASDisplayNode {
node.textImageNode.image = textImage
node.accessibilityLabel = item.item.title
node.accessibilityTraits = [.button]
node.imageNode.image = image
node.contextTextImageNode.image = contextTextImage
node.contextImageNode.image = contextImage
@ -576,6 +579,7 @@ class TabBarNode: ASDisplayNode {
let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
node.textImageNode.image = textImage
node.accessibilityLabel = item.item.title
node.accessibilityTraits = [.button, .selected]
node.imageNode.image = image
node.contextTextImageNode.image = contextTextImage
node.contextImageNode.image = contextImage
@ -608,6 +612,7 @@ class TabBarNode: ASDisplayNode {
node.textImageNode.image = textImage
node.accessibilityLabel = item.item.title
node.accessibilityTraits = [.button]
node.imageNode.image = image
node.contextTextImageNode.image = contextTextImage
node.contextImageNode.image = contextImage
@ -706,6 +711,7 @@ class TabBarNode: ASDisplayNode {
node.containerNode.frame = CGRect(origin: CGPoint(), size: nodeFrame.size)
node.hitTestSlop = UIEdgeInsets(top: -3.0, left: -horizontalHitTestInset, bottom: -3.0, right: -horizontalHitTestInset)
node.containerNode.hitTestSlop = UIEdgeInsets(top: -3.0, left: -horizontalHitTestInset, bottom: -3.0, right: -horizontalHitTestInset)
node.accessibilityFrame = nodeFrame.insetBy(dx: -horizontalHitTestInset, dy: 0.0).offsetBy(dx: 0.0, dy: size.height - nodeSize.height - bottomInset)
if node.ringColor == nil {
node.imageNode.frame = CGRect(origin: CGPoint(), size: nodeFrame.size)
}

View File

@ -188,7 +188,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
case .x0_5:
self.rateButton.setContent(.image(optionsRateImage(rate: "0.5X", color: self.theme.rootController.navigationBar.accentTextColor)))
case .x1:
self.rateButton.setContent(.image(optionsRateImage(rate: "2X", color: self.theme.rootController.navigationBar.controlColor)))
self.rateButton.setContent(.image(optionsRateImage(rate: "1X", color: self.theme.rootController.navigationBar.controlColor)))
self.rateButton.accessibilityLabel = self.strings.VoiceOver_Media_PlaybackRate
self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRateNormal
self.rateButton.accessibilityHint = self.strings.VoiceOver_Media_PlaybackRateChange
@ -378,7 +378,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
case .x0_5:
self.rateButton.setContent(.image(optionsRateImage(rate: "0.5X", color: self.theme.rootController.navigationBar.accentTextColor)))
case .x1:
self.rateButton.setContent(.image(optionsRateImage(rate: "2X", color: self.theme.rootController.navigationBar.controlColor)))
self.rateButton.setContent(.image(optionsRateImage(rate: "1X", color: self.theme.rootController.navigationBar.controlColor)))
case .x1_5:
self.rateButton.setContent(.image(optionsRateImage(rate: "1.5X", color: self.theme.rootController.navigationBar.accentTextColor)))
case .x2:
@ -511,6 +511,8 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
if let rate = self.playbackBaseRate {
switch rate {
case .x1:
nextRate = .x1_5
case .x1_5:
nextRate = .x2
default:
nextRate = .x1

View File

@ -687,41 +687,41 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
}
strongSelf.context.sharedContext.mediaManager.playlistControl(.setBaseRate(baseRate), type: type)
var hasTooltip = false
strongSelf.forEachController({ controller in
if let controller = controller as? UndoOverlayController {
hasTooltip = true
controller.dismissWithCommitAction()
}
return true
})
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let slowdown: Bool?
if baseRate == .x1 {
slowdown = true
} else if baseRate == .x2 {
slowdown = false
} else {
slowdown = nil
}
if let slowdown = slowdown {
strongSelf.present(
UndoOverlayController(
presentationData: presentationData,
content: .audioRate(
slowdown: slowdown,
text: slowdown ? presentationData.strings.Conversation_AudioRateTooltipNormal : presentationData.strings.Conversation_AudioRateTooltipSpeedUp
),
elevatedLayout: false,
animateInAsReplacement: hasTooltip,
action: { action in
return true
}
),
in: .current
)
}
// var hasTooltip = false
// strongSelf.forEachController({ controller in
// if let controller = controller as? UndoOverlayController {
// hasTooltip = true
// controller.dismissWithCommitAction()
// }
// return true
// })
//
// let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
// let slowdown: Bool?
// if baseRate == .x1 {
// slowdown = true
// } else if baseRate == .x2 {
// slowdown = false
// } else {
// slowdown = nil
// }
// if let slowdown = slowdown {
// strongSelf.present(
// UndoOverlayController(
// presentationData: presentationData,
// content: .audioRate(
// slowdown: slowdown,
// text: slowdown ? presentationData.strings.Conversation_AudioRateTooltipNormal : presentationData.strings.Conversation_AudioRateTooltipSpeedUp
// ),
// elevatedLayout: false,
// animateInAsReplacement: hasTooltip,
// action: { action in
// return true
// }
// ),
// in: .current
// )
// }
})
}
mediaAccessoryPanel.togglePlayPause = { [weak self] in

View File

@ -436,6 +436,9 @@ final class CallControllerButtonsNode: ASDisplayNode {
}
let buttonContent: CallControllerButtonItemNode.Content
let buttonText: String
var buttonAccessibilityLabel = ""
var buttonAccessibilityValue = ""
var buttonAccessibilityTraits: UIAccessibilityTraits = [.button]
switch button.button {
case .accept:
buttonContent = CallControllerButtonItemNode.Content(
@ -443,6 +446,7 @@ final class CallControllerButtonsNode: ASDisplayNode {
image: .accept
)
buttonText = strings.Call_Accept
buttonAccessibilityLabel = buttonText
case let .end(type):
buttonContent = CallControllerButtonItemNode.Content(
appearance: .color(.red),
@ -456,6 +460,11 @@ final class CallControllerButtonsNode: ASDisplayNode {
case .end:
buttonText = strings.Call_End
}
if !buttonText.isEmpty {
buttonAccessibilityLabel = buttonText
} else {
buttonAccessibilityLabel = strings.Call_End
}
case let .enableCamera(isActivated, isEnabled, isInitializing, isScreencastActive):
buttonContent = CallControllerButtonItemNode.Content(
appearance: .blurred(isFilled: isActivated),
@ -464,6 +473,13 @@ final class CallControllerButtonsNode: ASDisplayNode {
hasProgress: isInitializing
)
buttonText = strings.Call_Camera
buttonAccessibilityLabel = buttonText
if !isEnabled {
buttonAccessibilityTraits.insert(.notEnabled)
}
if isActivated {
buttonAccessibilityTraits.insert(.selected)
}
case let .switchCamera(isEnabled):
buttonContent = CallControllerButtonItemNode.Content(
appearance: .blurred(isFilled: false),
@ -471,6 +487,10 @@ final class CallControllerButtonsNode: ASDisplayNode {
isEnabled: isEnabled
)
buttonText = strings.Call_Flip
buttonAccessibilityLabel = buttonText
if !isEnabled {
buttonAccessibilityTraits.insert(.notEnabled)
}
case let .soundOutput(value):
let image: CallControllerButtonItemNode.Content.Image
var isFilled = false
@ -484,30 +504,43 @@ final class CallControllerButtonsNode: ASDisplayNode {
case .bluetooth:
image = .bluetooth
title = strings.Call_Audio
buttonAccessibilityValue = "Bluetooth"
case .airpods:
image = .airpods
title = strings.Call_Audio
buttonAccessibilityValue = "Airpods"
case .airpodsPro:
image = .airpodsPro
title = strings.Call_Audio
buttonAccessibilityValue = "Airpods Pro"
case .airpodsMax:
image = .airpodsMax
title = strings.Call_Audio
buttonAccessibilityValue = "Airpods Max"
case .headphones:
image = .headphones
title = strings.Call_Audio
buttonAccessibilityValue = strings.Call_AudioRouteHeadphones
}
buttonContent = CallControllerButtonItemNode.Content(
appearance: .blurred(isFilled: isFilled),
image: image
)
buttonText = title
buttonAccessibilityLabel = buttonText
if isFilled {
buttonAccessibilityTraits.insert(.selected)
}
case let .mute(isMuted):
buttonContent = CallControllerButtonItemNode.Content(
appearance: .blurred(isFilled: isMuted),
image: .mute
)
buttonText = strings.Call_Mute
buttonAccessibilityLabel = buttonText
if isMuted {
buttonAccessibilityTraits.insert(.selected)
}
}
var buttonDelay = 0.0
if animatePositionsWithDelay {
@ -526,6 +559,10 @@ final class CallControllerButtonsNode: ASDisplayNode {
}
buttonTransition.updateFrame(node: buttonNode, frame: button.frame, delay: buttonDelay)
buttonNode.update(size: button.frame.size, content: buttonContent, text: buttonText, transition: buttonTransition)
buttonNode.accessibilityLabel = buttonAccessibilityLabel
buttonNode.accessibilityValue = buttonAccessibilityValue
buttonNode.accessibilityTraits = buttonAccessibilityTraits
if animateButtonIn {
buttonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}

View File

@ -501,6 +501,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
self.buttonsNode = CallControllerButtonsNode(strings: self.presentationData.strings)
self.toastNode = CallControllerToastContainerNode(strings: self.presentationData.strings)
self.keyButtonNode = CallControllerKeyButton()
self.keyButtonNode.accessibilityElementsHidden = false
super.init()
@ -510,6 +511,8 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
self.containerTransformationNode.addSubnode(self.containerNode)
self.backButtonNode.setTitle(presentationData.strings.Common_Back, with: Font.regular(17.0), with: .white, for: [])
self.backButtonNode.accessibilityLabel = presentationData.strings.Call_VoiceOver_Minimize
self.backButtonNode.accessibilityTraits = [.button]
self.backButtonNode.hitTestSlop = UIEdgeInsets(top: -8.0, left: -20.0, bottom: -8.0, right: -8.0)
self.backButtonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {

View File

@ -40,6 +40,9 @@ final class CallControllerStatusNode: ASDisplayNode {
private let receptionNode: CallControllerReceptionNode
private let logoNode: ASImageNode
private let titleActivateAreaNode: AccessibilityAreaNode
private let statusActivateAreaNode: AccessibilityAreaNode
var title: String = ""
var subtitle: String = ""
var status: CallControllerStatusValue = .text(string: "", displayLogo: false) {
@ -118,6 +121,12 @@ final class CallControllerStatusNode: ASDisplayNode {
self.logoNode.image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallTitleLogo"), color: .white)
self.logoNode.isHidden = true
self.titleActivateAreaNode = AccessibilityAreaNode()
self.titleActivateAreaNode.accessibilityTraits = .staticText
self.statusActivateAreaNode = AccessibilityAreaNode()
self.statusActivateAreaNode.accessibilityTraits = [.staticText, .updatesFrequently]
super.init()
self.isUserInteractionEnabled = false
@ -127,6 +136,9 @@ final class CallControllerStatusNode: ASDisplayNode {
self.statusContainerNode.addSubnode(self.statusNode)
self.statusContainerNode.addSubnode(self.receptionNode)
self.statusContainerNode.addSubnode(self.logoNode)
self.addSubnode(self.titleActivateAreaNode)
self.addSubnode(self.statusActivateAreaNode)
}
deinit {
@ -191,6 +203,9 @@ final class CallControllerStatusNode: ASDisplayNode {
let _ = statusApply()
let _ = statusMeasureApply()
self.titleActivateAreaNode.accessibilityLabel = self.title
self.statusActivateAreaNode.accessibilityLabel = statusText
self.titleNode.frame = CGRect(origin: CGPoint(x: floor((constrainedWidth - titleLayout.size.width) / 2.0), y: 0.0), size: titleLayout.size)
self.statusContainerNode.frame = CGRect(origin: CGPoint(x: 0.0, y: titleLayout.size.height + spacing), size: CGSize(width: constrainedWidth, height: statusLayout.size.height))
self.statusNode.frame = CGRect(origin: CGPoint(x: floor((constrainedWidth - statusMeasureLayout.size.width) / 2.0) + statusOffset, y: 0.0), size: statusLayout.size)
@ -201,6 +216,9 @@ final class CallControllerStatusNode: ASDisplayNode {
self.logoNode.frame = CGRect(origin: CGPoint(x: self.statusNode.frame.minX + firstLineOffset - image.size.width - 7.0, y: 5.0), size: image.size)
}
self.titleActivateAreaNode.frame = self.titleNode.frame
self.statusActivateAreaNode.frame = self.statusContainerNode.frame
return titleLayout.size.height + spacing + statusLayout.size.height
}
}

View File

@ -6296,6 +6296,8 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
return nil
}
}
let tempFile = EngineTempBox.shared.tempFile(fileName: "video.mp4")
let uploadInterface = LegacyLiveUploadInterface(context: context)
let signal: SSignal
if let url = asset as? URL, url.absoluteString.hasSuffix(".jpg"), let data = try? Data(contentsOf: url, options: [.mappedRead]), let image = UIImage(data: data), let entityRenderer = entityRenderer {
@ -6311,14 +6313,14 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
})
signal = durationSignal.map(toSignal: { duration -> SSignal in
if let duration = duration as? Double {
return TGMediaVideoConverter.renderUIImage(image, duration: duration, adjustments: adjustments, watcher: nil, entityRenderer: entityRenderer)!
return TGMediaVideoConverter.renderUIImage(image, duration: duration, adjustments: adjustments, path: tempFile.path, watcher: nil, entityRenderer: entityRenderer)!
} else {
return SSignal.single(nil)
}
})
} else if let asset = asset as? AVAsset {
signal = TGMediaVideoConverter.convert(asset, adjustments: adjustments, watcher: uploadInterface, entityRenderer: entityRenderer)!
signal = TGMediaVideoConverter.convert(asset, adjustments: adjustments, path: tempFile.path, watcher: uploadInterface, entityRenderer: entityRenderer)!
} else {
signal = SSignal.complete()
}
@ -6344,6 +6346,8 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
}
account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
subscriber.putNext(resource)
EngineTempBox.shared.dispose(tempFile)
}
}
subscriber.putCompletion()

View File

@ -3513,6 +3513,7 @@ func replayFinalState(
transaction.updateMessage(id, update: { previousMessage in
var updatedFlags = message.flags
var updatedLocalTags = message.localTags
var updatedAttributes = message.attributes
if previousMessage.localTags.contains(.OutgoingLiveLocation) {
updatedLocalTags.insert(.OutgoingLiveLocation)
}
@ -3528,10 +3529,20 @@ func replayFinalState(
return current
})
if previousMessage.text == message.text {
let previousEntities = previousMessage.textEntitiesAttribute?.entities ?? []
let updatedEntities = (message.attributes.first(where: { $0 is TextEntitiesMessageAttribute }) as? TextEntitiesMessageAttribute)?.entities ?? []
if previousEntities == updatedEntities, let translation = previousMessage.attributes.first(where: { $0 is TranslationMessageAttribute }) as? TranslationMessageAttribute {
if message.attributes.firstIndex(where: { $0 is TranslationMessageAttribute }) == nil {
updatedAttributes.append(translation)
}
}
}
if let message = locallyRenderedMessage(message: message, peers: peers) {
generatedEvent = reactionGeneratedEvent(previousMessage.reactionsAttribute, message.reactionsAttribute, message: message, transaction: transaction)
}
return .update(message.withUpdatedLocalTags(updatedLocalTags).withUpdatedFlags(updatedFlags))
return .update(message.withUpdatedLocalTags(updatedLocalTags).withUpdatedFlags(updatedFlags).withUpdatedAttributes(updatedAttributes))
})
if let generatedEvent = generatedEvent {
addedReactionEvents.append(generatedEvent)

View File

@ -35,7 +35,13 @@ public enum SetCustomPeerPhotoMode {
}
func _internal_updateContactPhoto(account: Account, peerId: PeerId, resource: MediaResource?, videoResource: MediaResource?, videoStartTimestamp: Double?, markup: UploadPeerPhotoMarkup?, mode: SetCustomPeerPhotoMode, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
return _internal_updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: peerId, photo: resource.flatMap({ _internal_uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: $0) }), video: videoResource.flatMap({ _internal_uploadedPeerVideo(postbox: account.postbox, network: account.network, messageMediaPreuploadManager: account.messageMediaPreuploadManager, resource: $0) |> map(Optional.init) }), videoStartTimestamp: videoStartTimestamp, markup: markup, customPeerPhotoMode: mode, mapResourceToAvatarSizes: mapResourceToAvatarSizes)
let photo: Signal<UploadedPeerPhotoData, NoError>?
if videoResource == nil && markup != nil, let resource = resource {
photo = .single(UploadedPeerPhotoData.withResource(resource))
} else {
photo = resource.flatMap({ _internal_uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: $0) })
}
return _internal_updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: peerId, photo: photo, video: videoResource.flatMap({ _internal_uploadedPeerVideo(postbox: account.postbox, network: account.network, messageMediaPreuploadManager: account.messageMediaPreuploadManager, resource: $0) |> map(Optional.init) }), videoStartTimestamp: videoStartTimestamp, markup: markup, customPeerPhotoMode: mode, mapResourceToAvatarSizes: mapResourceToAvatarSizes)
}
public struct UploadedPeerPhotoData {
@ -208,7 +214,7 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state
if let _ = videoEmojiMarkup {
flags |= (1 << 5)
}
request = network.request(Api.functions.photos.uploadContactProfilePhoto(flags: flags, userId: inputUser, file: file, video: videoFile, videoStartTs: videoStartTimestamp, videoEmojiMarkup: videoEmojiMarkup))
request = network.request(Api.functions.photos.uploadContactProfilePhoto(flags: flags, userId: inputUser, file: photoFile, video: videoFile, videoStartTs: videoStartTimestamp, videoEmojiMarkup: videoEmojiMarkup))
} else {
request = .complete()
}

View File

@ -418,7 +418,7 @@ public func foldLineBreaks(_ text: NSAttributedString) -> NSAttributedString {
while true {
if let range = remainingString.string.range(of: "\n") {
let mappedRange = NSRange(range, in: remainingString.string)
lines.append(remainingString.attributedSubstring(from: NSRange(location: 0, length: mappedRange.upperBound)))
lines.append(remainingString.attributedSubstring(from: NSRange(location: 0, length: mappedRange.upperBound - 1)))
remainingString.replaceCharacters(in: NSRange(location: 0, length: mappedRange.upperBound), with: "")
} else {
if lines.isEmpty {

View File

@ -77,7 +77,7 @@ final class BackgroundColorComponent: Component {
let itemSize = CGSize(width: 30.0, height: 30.0)
let sideInset: CGFloat = 12.0
let height: CGFloat = 50.0
let delta = (availableSize.width - sideInset * 2.0 - CGFloat(values.count) * itemSize.width) / CGFloat(values.count - 1)
let delta = floorToScreenPixels((availableSize.width - sideInset * 2.0 - CGFloat(values.count) * itemSize.width) / CGFloat(values.count - 1))
for i in 0 ..< values.count {
let view: ComponentView<Empty>

View File

@ -237,6 +237,9 @@ public final class ChatListHeaderComponent: Component {
self.titleView.attributedText = NSAttributedString(string: title, font: Font.regular(17.0), textColor: theme.rootController.navigationBar.accentTextColor)
let titleSize = self.titleView.updateLayout(CGSize(width: 100.0, height: 44.0))
self.accessibilityLabel = title
self.accessibilityTraits = [.button]
if self.currentColor != theme.rootController.navigationBar.accentTextColor {
self.currentColor = theme.rootController.navigationBar.accentTextColor
self.arrowView.image = NavigationBarTheme.generateBackArrowImage(color: theme.rootController.navigationBar.accentTextColor)

View File

@ -337,12 +337,6 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
self.window = window
self.nativeWindow = window
let launchIconSize = CGSize(width: 99.0, height: 99.0)
let launchIconView = UIImageView(image: UIImage(bundleImageName: "Components/LaunchLogo"))
launchIconView.autoresizingMask = [.flexibleTopMargin, .flexibleLeftMargin, .flexibleRightMargin, .flexibleBottomMargin]
launchIconView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((hostView.containerView.frame.width - launchIconSize.width) / 2.0), y: floorToScreenPixels((hostView.containerView.frame.height - launchIconSize.height) / 2.0)), size: launchIconSize)
hostView.containerView.addSubview(launchIconView)
let clearNotificationsManager = ClearNotificationsManager(getNotificationIds: { completion in
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().getDeliveredNotifications(completionHandler: { notifications in
@ -1072,7 +1066,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
let startTime = CFAbsoluteTimeGetCurrent()
self.contextDisposable.set((self.context.get()
|> deliverOnMainQueue).start(next: { [weak launchIconView] context in
|> deliverOnMainQueue).start(next: { context in
print("Application: context took \(CFAbsoluteTimeGetCurrent() - startTime) to become available")
var network: Network?
@ -1105,13 +1099,6 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
self.mainWindow.debugAction = nil
self.mainWindow.viewController = context.rootController
if let launchIconView {
launchIconView.alpha = 0.0
launchIconView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak launchIconView] _ in
launchIconView?.removeFromSuperview()
})
}
if firstTime {
let layer = context.rootController.view.layer
layer.allowsGroupOpacity = true
@ -1158,7 +1145,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
let authContextReadyDisposable = MetaDisposable()
self.authContextDisposable.set((self.authContext.get()
|> deliverOnMainQueue).start(next: { [weak launchIconView] context in
|> deliverOnMainQueue).start(next: { context in
var network: Network?
if let context = context {
network = context.account.network
@ -1211,17 +1198,6 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|> deliverOnMainQueue).start(next: { _ in
progressDisposable.dispose()
self.mainWindow.present(context.rootController, on: .root)
if let launchIconView {
if context.rootController.topViewController is AuthorizationSequenceSplashController {
context.rootController.view.addSubview(launchIconView)
Queue.mainQueue().after(0.01, {
launchIconView.removeFromSuperview()
})
} else {
launchIconView.removeFromSuperview()
}
}
}))
} else {
authContextReadyDisposable.set(nil)

View File

@ -65,6 +65,8 @@ private final class ChatButtonKeyboardInputButtonNode: HighlightTrackingButtonNo
super.init()
self.accessibilityTraits = [.button]
self.addSubnode(self.backgroundContainerNode)
self.backgroundContainerNode.addSubnode(self.backgroundColorNode)
@ -94,6 +96,7 @@ private final class ChatButtonKeyboardInputButtonNode: HighlightTrackingButtonNo
override func setAttributedTitle(_ title: NSAttributedString, for state: UIControl.State) {
self.textNode.attributedText = title
self.accessibilityLabel = title.string
}
private var absoluteRect: (CGRect, CGSize)?

View File

@ -312,6 +312,7 @@ final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
}
self.button.setTitle(title, with: Font.regular(17.0), with: color, for: [])
self.button.accessibilityLabel = title
} else {
self.action = nil
}

View File

@ -4304,36 +4304,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.chatTitleView = ChatTitleView(context: self.context, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, animationCache: controllerInteraction.presentationContext.animationCache, animationRenderer: controllerInteraction.presentationContext.animationRenderer)
self.navigationItem.titleView = self.chatTitleView
self.chatTitleView?.pressed = { [weak self] in
if let strongSelf = self {
if strongSelf.chatLocation == .peer(id: strongSelf.context.account.peerId) {
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer, let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: true, requestsContext: nil) {
strongSelf.effectiveNavigationController?.pushViewController(infoController)
}
} else {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
return $0.updatedTitlePanelContext {
if let index = $0.firstIndex(where: {
switch $0 {
case .chatInfo:
return true
default:
return false
}
}) {
var updatedContexts = $0
updatedContexts.remove(at: index)
return updatedContexts
} else {
var updatedContexts = $0
updatedContexts.append(.chatInfo)
return updatedContexts.sorted()
}
}
})
}
}
}
self.chatTitleView?.longPressed = { [weak self] in
if let strongSelf = self, let peerView = strongSelf.peerView, let peer = peerView.peers[peerView.peerId], peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil && !strongSelf.presentationInterfaceState.isNotAccessible {
strongSelf.interfaceInteraction?.beginMessageSearch(.everything, "")
@ -8317,22 +8287,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: interactive, { current in
return current.updatedTitlePanelContext {
if let index = $0.firstIndex(where: {
switch $0 {
case .chatInfo:
return true
default:
return false
}
}) {
var updatedContexts = $0
updatedContexts.remove(at: index)
return updatedContexts
} else {
return $0
}
}.updatedSearch(current.search == nil ? ChatSearchData(domain: domain).withUpdatedQuery(query) : current.search?.withUpdatedDomain(domain).withUpdatedQuery(query))
return current.updatedSearch(current.search == nil ? ChatSearchData(domain: domain).withUpdatedQuery(query) : current.search?.withUpdatedDomain(domain).withUpdatedQuery(query))
})
strongSelf.updateItemNodesSearchTextHighlightStates()
})
@ -9613,17 +9568,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation else {
return
}
strongSelf.updateChatPresentationInterfaceState(interactive: true, { state in
return state.updatedTitlePanelContext({
$0.filter({ item in
if case .chatInfo = item {
return false
} else {
return true
}
})
})
})
let _ = (strongSelf.context.engine.peers.updatePeersGroupIdInteractively(peerIds: [peerId], groupId: .root)
|> deliverOnMainQueue).start()
}, openLinkEditing: { [weak self] in
@ -11070,30 +11014,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return interfaceState
}).start()
}
override public func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
self.updateChatPresentationInterfaceState(animated: false, interactive: false, {
$0.updatedTitlePanelContext {
if let index = $0.firstIndex(where: {
switch $0 {
case .chatInfo:
return true
default:
return false
}
}) {
var updatedContexts = $0
updatedContexts.remove(at: index)
return updatedContexts
} else {
return $0
}
}
})
}
override public func viewWillLeaveNavigation() {
self.chatDisplayNode.willNavigateAway()
}
@ -11719,7 +11640,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
private func navigationButtonAction(_ action: ChatNavigationButtonAction) {
switch action {
case .spacer:
case .spacer, .toggleInfoPanel:
break
case .cancelMessageSelection:
self.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } })
@ -12204,27 +12125,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
case .feed:
break
}
case .toggleInfoPanel:
self.updateChatPresentationInterfaceState(animated: true, interactive: true, {
return $0.updatedTitlePanelContext {
if let index = $0.firstIndex(where: {
switch $0 {
case .chatInfo:
return true
default:
return false
}
}) {
var updatedContexts = $0
updatedContexts.remove(at: index)
return updatedContexts
} else {
var updatedContexts = $0
updatedContexts.append(.chatInfo)
return updatedContexts.sorted()
}
}
})
}
}

View File

@ -1362,7 +1362,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
}
})
}
if currentIsVisible {
if currentIsVisible && currentlyPlayingVideo {
updatedScrollPosition = .index(index: .message(currentlyPlayingMessageId), position: .center(.bottom), directionHint: .Up, animated: true, highlight: true, displayLink: true)
scrollAnimationCurve = .Spring(duration: 0.4)
}

View File

@ -1,230 +0,0 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import Postbox
import TelegramCore
import TelegramPresentationData
import ChatPresentationInterfaceState
private enum ChatInfoTitleButton {
case search
case info
case mute
case unmute
case call
case report
case unarchive
func title(_ strings: PresentationStrings) -> String {
switch self {
case .search:
return strings.Common_Search
case .info:
return strings.Conversation_Info
case .mute:
return strings.Conversation_TitleMute
case .unmute:
return strings.Conversation_TitleUnmute
case .call:
return strings.Conversation_Call
case .report:
return strings.ReportPeer_Report
case .unarchive:
return strings.ChatList_UnarchiveAction
}
}
func icon(_ theme: PresentationTheme) -> UIImage? {
switch self {
case .search:
return PresentationResourcesChat.chatTitlePanelSearchImage(theme)
case .info:
return PresentationResourcesChat.chatTitlePanelInfoImage(theme)
case .mute:
return PresentationResourcesChat.chatTitlePanelMuteImage(theme)
case .unmute:
return PresentationResourcesChat.chatTitlePanelUnmuteImage(theme)
case .call:
return PresentationResourcesChat.chatTitlePanelCallImage(theme)
case .report:
return PresentationResourcesChat.chatTitlePanelReportImage(theme)
case .unarchive:
return PresentationResourcesChat.chatTitlePanelUnarchiveImage(theme)
}
}
}
private func peerButtons(_ peer: Peer, interfaceState: ChatPresentationInterfaceState) -> [ChatInfoTitleButton] {
let muteAction: ChatInfoTitleButton
if interfaceState.peerIsMuted {
muteAction = .unmute
} else {
muteAction = .mute
}
let infoButton: ChatInfoTitleButton
if interfaceState.isArchived {
infoButton = .unarchive
} else {
infoButton = .info
}
if let peer = peer as? TelegramUser {
var buttons: [ChatInfoTitleButton] = [.search, muteAction]
if peer.botInfo == nil && interfaceState.callsAvailable {
buttons.append(.call)
}
buttons.append(infoButton)
return buttons
} else if let _ = peer as? TelegramSecretChat {
var buttons: [ChatInfoTitleButton] = [.search, muteAction]
buttons.append(.call)
buttons.append(.info)
return buttons
} else if let channel = peer as? TelegramChannel {
if channel.flags.contains(.isCreator) || channel.addressName == nil {
return [.search, muteAction, infoButton]
} else {
return [.search, .report, muteAction, infoButton]
}
} else if let group = peer as? TelegramGroup {
if case .creator = group.role {
return [.search, muteAction, infoButton]
} else {
return [.search, muteAction, infoButton]
}
} else {
return [.search, muteAction, infoButton]
}
}
private let buttonFont = Font.medium(10.0)
private final class ChatInfoTitlePanelButtonNode: HighlightableButtonNode {
init() {
super.init()
self.displaysAsynchronously = false
self.imageNode.displayWithoutProcessing = true
self.imageNode.displaysAsynchronously = false
self.titleNode.displaysAsynchronously = false
self.laysOutHorizontally = false
}
func setup(text: String, color: UIColor, icon: UIImage?) {
self.setTitle(text, with: buttonFont, with: color, for: [])
self.setImage(icon, for: [])
if let icon = icon {
self.contentSpacing = max(0.0, 32.0 - icon.size.height)
}
}
}
final class ChatInfoTitlePanelNode: ChatTitleAccessoryPanelNode {
private var theme: PresentationTheme?
private let separatorNode: ASDisplayNode
private var buttons: [(ChatInfoTitleButton, ChatInfoTitlePanelButtonNode)] = []
override init() {
self.separatorNode = ASDisplayNode()
self.separatorNode.isLayerBacked = true
super.init()
self.addSubnode(self.separatorNode)
}
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> LayoutResult {
let themeUpdated = self.theme !== interfaceState.theme
self.theme = interfaceState.theme
let panelHeight: CGFloat = 55.0
if themeUpdated {
self.separatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor
}
let updatedButtons: [ChatInfoTitleButton]
switch interfaceState.chatLocation {
case .peer:
if let peer = interfaceState.renderedPeer?.peer {
updatedButtons = peerButtons(peer, interfaceState: interfaceState)
} else {
updatedButtons = []
}
case .replyThread, .feed:
updatedButtons = []
}
var buttonsUpdated = false
if self.buttons.count != updatedButtons.count {
buttonsUpdated = true
} else {
for i in 0 ..< updatedButtons.count {
if self.buttons[i].0 != updatedButtons[i] {
buttonsUpdated = true
break
}
}
}
if buttonsUpdated || themeUpdated {
for (_, buttonNode) in self.buttons {
buttonNode.removeFromSupernode()
}
self.buttons.removeAll()
for button in updatedButtons {
let buttonNode = ChatInfoTitlePanelButtonNode()
buttonNode.laysOutHorizontally = false
buttonNode.setup(text: button.title(interfaceState.strings), color: interfaceState.theme.chat.inputPanel.panelControlAccentColor, icon: button.icon(interfaceState.theme))
buttonNode.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: [.touchUpInside])
self.addSubnode(buttonNode)
self.buttons.append((button, buttonNode))
}
}
if !self.buttons.isEmpty {
let buttonWidth = floor((width - leftInset - rightInset) / CGFloat(self.buttons.count))
var nextButtonOrigin: CGFloat = leftInset
for (_, buttonNode) in self.buttons {
buttonNode.frame = CGRect(origin: CGPoint(x: nextButtonOrigin, y: 0.0), size: CGSize(width: buttonWidth, height: panelHeight))
nextButtonOrigin += buttonWidth
}
}
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel)))
return LayoutResult(backgroundHeight: panelHeight, insetHeight: panelHeight)
}
@objc func buttonPressed(_ node: HighlightableButtonNode) {
for (button, buttonNode) in self.buttons {
if buttonNode === node {
switch button {
case .info:
self.interfaceInteraction?.openPeerInfo()
case .mute:
self.interfaceInteraction?.togglePeerNotifications()
case .unmute:
self.interfaceInteraction?.togglePeerNotifications()
case .search:
self.interfaceInteraction?.beginMessageSearch(.everything, "")
case .call:
self.interfaceInteraction?.beginCall(false)
case .report:
self.interfaceInteraction?.reportPeer()
case .unarchive:
self.interfaceInteraction?.unarchiveChat()
}
break
}
}
}
}

View File

@ -42,7 +42,7 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat
break loop
}
}
case .chatInfo, .requestInProgress, .toastAlert, .inviteRequests:
case .requestInProgress, .toastAlert, .inviteRequests:
selectedContext = context
break loop
}
@ -120,14 +120,6 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat
panel.interfaceInteraction = interfaceInteraction
return panel
}
case .chatInfo:
if let currentPanel = currentPanel as? ChatInfoTitlePanelNode {
return currentPanel
} else {
let panel = ChatInfoTitlePanelNode()
panel.interfaceInteraction = interfaceInteraction
return panel
}
case .requestInProgress:
if let currentPanel = currentPanel as? ChatRequestInProgressTitlePanelNode {
return currentPanel

View File

@ -119,6 +119,8 @@ final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode {
private var avatarsContent: AnimatedAvatarSetContext.Content?
private let avatarsNode: AnimatedAvatarSetNode
private let activateAreaNode: AccessibilityAreaNode
private var theme: PresentationTheme?
private var peerId: PeerId?
@ -138,6 +140,9 @@ final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode {
self.avatarsContext = AnimatedAvatarSetContext()
self.avatarsNode = AnimatedAvatarSetNode()
self.activateAreaNode = AccessibilityAreaNode()
self.activateAreaNode.accessibilityTraits = .button
super.init()
self.addSubnode(self.separatorNode)
@ -146,6 +151,8 @@ final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode {
self.addSubnode(self.closeButton)
self.addSubnode(self.avatarsNode)
self.addSubnode(self.activateAreaNode)
}
@ -198,6 +205,9 @@ final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode {
transition.updateFrame(node: self.avatarsNode, frame: CGRect(origin: CGPoint(x: leftInset + 8.0, y: floor((panelHeight - avatarsSize.height) / 2.0)), size: avatarsSize))
}
self.activateAreaNode.frame = CGRect(origin: .zero, size: CGSize(width: width, height: panelHeight))
self.activateAreaNode.accessibilityLabel = interfaceState.strings.Conversation_RequestsToJoin(self.count)
return LayoutResult(backgroundHeight: initialPanelHeight, insetHeight: panelHeight)
}

View File

@ -1136,7 +1136,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
}
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .regular)
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .regular, associatedData: item.associatedData)
var isReplyThread = false
if case .replyThread = item.chatLocation {

View File

@ -391,7 +391,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
}
}
let dateText = stringForMessageTimestampStatus(accountPeerId: context.account.peerId, message: message, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, strings: presentationData.strings)
let dateText = stringForMessageTimestampStatus(accountPeerId: context.account.peerId, message: message, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, strings: presentationData.strings, associatedData: associatedData)
var webpageGalleryMediaCount: Int?
for media in message.media {

View File

@ -1800,7 +1800,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
} else {
dateFormat = .regular
}
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat)
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, associatedData: item.associatedData)
let statusType: ChatMessageDateAndStatusType
if incoming {

View File

@ -167,7 +167,7 @@ class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
}
}
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings)
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, associatedData: item.associatedData)
let statusText: String
if let callDuration = callDuration, callDuration > 1 {

View File

@ -188,7 +188,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
}
}
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings)
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, associatedData: item.associatedData)
let statusType: ChatMessageDateAndStatusType?
switch position {

View File

@ -815,7 +815,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
edited = true
}
let dateText = stringForMessageTimestampStatus(accountPeerId: arguments.context.account.peerId, message: arguments.message, dateTimeFormat: arguments.presentationData.dateTimeFormat, nameDisplayOrder: arguments.presentationData.nameDisplayOrder, strings: arguments.presentationData.strings)
let dateText = stringForMessageTimestampStatus(accountPeerId: arguments.context.account.peerId, message: arguments.message, dateTimeFormat: arguments.presentationData.dateTimeFormat, nameDisplayOrder: arguments.presentationData.nameDisplayOrder, strings: arguments.presentationData.strings, associatedData: arguments.associatedData)
let displayReactionsInline = shouldDisplayInlineDateReactions(message: arguments.message, isPremium: arguments.associatedData.isPremium, forceInline: arguments.associatedData.forceInlineReactions)
var reactionSettings: ChatMessageDateAndStatusNode.TrailingReactionSettings?

View File

@ -487,7 +487,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
}
}
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .regular)
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .regular, associatedData: item.associatedData)
let maxDateAndStatusWidth: CGFloat
if case .bubble = statusDisplayType {

View File

@ -214,6 +214,7 @@ final class ChatMessageAccessibilityData {
loop: for media in message.media {
if let _ = media as? TelegramMediaImage {
traits.insert(.image)
if isIncoming {
if announceIncomingAuthors, let authorName = authorName {
label = item.presentationData.strings.VoiceOver_Chat_PhotoFrom(authorName).string

View File

@ -205,7 +205,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
}
}
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings)
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, associatedData: item.associatedData)
let statusType: ChatMessageDateAndStatusType?
switch position {

View File

@ -237,8 +237,8 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
}
}
}
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings)
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, associatedData: item.associatedData)
let statusType: ChatMessageDateAndStatusType?
switch preparePosition {

View File

@ -1069,7 +1069,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
}
}
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings)
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, associatedData: item.associatedData)
let statusType: ChatMessageDateAndStatusType?
switch position {

View File

@ -71,7 +71,7 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode {
}
}
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings)
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, associatedData: item.associatedData)
let statusType: ChatMessageDateAndStatusType?
switch position {

View File

@ -561,7 +561,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
}
}
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .regular)
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .regular, associatedData: item.associatedData)
var isReplyThread = false
if case .replyThread = item.chatLocation {

View File

@ -181,7 +181,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
} else {
dateFormat = .regular
}
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat)
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, associatedData: item.associatedData)
let statusType: ChatMessageDateAndStatusType?
var displayStatus = false

View File

@ -859,6 +859,8 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
self.cancelButton = HighlightableButtonNode()
self.cancelButton.setImage(closeButtonImage(theme: self.presentationData.theme), for: .normal)
self.cancelButton.accessibilityLabel = self.presentationData.strings.Common_Close
self.cancelButton.accessibilityTraits = [.button]
self.switchThemeButton = HighlightTrackingButtonNode()
self.animationContainerNode = ASDisplayNode()

View File

@ -9,6 +9,8 @@ final class ChatRequestInProgressTitlePanelNode: ChatTitleAccessoryPanelNode {
private let separatorNode: ASDisplayNode
private let titleNode: ImmediateTextNode
private let activateAreaNode: AccessibilityAreaNode
private var theme: PresentationTheme?
private var strings: PresentationStrings?
@ -19,10 +21,15 @@ final class ChatRequestInProgressTitlePanelNode: ChatTitleAccessoryPanelNode {
self.titleNode = ImmediateTextNode()
self.titleNode.maximumNumberOfLines = 1
self.activateAreaNode = AccessibilityAreaNode()
self.activateAreaNode.accessibilityTraits = .staticText
super.init()
self.addSubnode(self.titleNode)
self.addSubnode(self.separatorNode)
self.addSubnode(self.activateAreaNode)
}
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> LayoutResult {
@ -46,6 +53,9 @@ final class ChatRequestInProgressTitlePanelNode: ChatTitleAccessoryPanelNode {
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel)))
self.activateAreaNode.frame = CGRect(origin: .zero, size: CGSize(width: width, height: panelHeight))
self.activateAreaNode.accessibilityLabel = interfaceState.strings.Channel_NotificationLoading
return LayoutResult(backgroundHeight: panelHeight, insetHeight: panelHeight)
}
}

View File

@ -32,6 +32,8 @@ final class ChatTextInputAudioRecordingCancelIndicator: ASDisplayNode {
self.cancelButton = HighlightableButtonNode()
self.cancelButton.setTitle(strings.Common_Cancel, with: cancelFont, with: theme.chat.inputPanel.panelControlAccentColor, for: [])
self.cancelButton.alpha = 0.0
self.cancelButton.accessibilityLabel = strings.Common_Cancel
self.cancelButton.accessibilityTraits = [.button]
self.strings = strings

View File

@ -8,6 +8,8 @@ final class ChatToastAlertPanelNode: ChatTitleAccessoryPanelNode {
private let separatorNode: ASDisplayNode
private let titleNode: ImmediateTextNode
private let activateAreaNode: AccessibilityAreaNode
private var textColor: UIColor = .black {
didSet {
if !self.textColor.isEqual(oldValue) {
@ -34,6 +36,9 @@ final class ChatToastAlertPanelNode: ChatTitleAccessoryPanelNode {
self.titleNode.maximumNumberOfLines = 1
self.titleNode.insets = UIEdgeInsets(top: 2.0, left: 2.0, bottom: 2.0, right: 2.0)
self.activateAreaNode = AccessibilityAreaNode()
self.activateAreaNode.accessibilityTraits = [.staticText]
super.init()
self.addSubnode(self.titleNode)
@ -51,6 +56,9 @@ final class ChatToastAlertPanelNode: ChatTitleAccessoryPanelNode {
let titleSize = self.titleNode.updateLayout(CGSize(width: width - leftInset - rightInset - 20.0, height: 100.0))
self.titleNode.frame = CGRect(origin: CGPoint(x: floor((width - titleSize.width) / 2.0), y: floor((panelHeight - titleSize.height) / 2.0)), size: titleSize)
self.activateAreaNode.frame = CGRect(origin: .zero, size: CGSize(width: width, height: panelHeight))
self.activateAreaNode.accessibilityLabel = self.titleNode.attributedText?.string ?? ""
return LayoutResult(backgroundHeight: panelHeight, insetHeight: panelHeight)
}
}

View File

@ -226,8 +226,8 @@ final class ChatTranslationPanelNode: ASDisplayNode {
}
}
topLanguages.append(contentsOf: popularTranslationLanguages)
topLanguages.append("")
var languages: [(String, String)] = []
let languageLocale = Locale(identifier: langCode)
@ -322,6 +322,8 @@ private final class TranslationContextReferenceContentSource: ContextReferenceCo
}
}
private let separatorHeight: CGFloat = 7.0
private final class TranslationLanguagesContextMenuContent: ContextControllerItemsContent {
private final class BackButtonNode: HighlightTrackingButtonNode {
let highlightBackgroundNode: ASDisplayNode
@ -445,7 +447,7 @@ private final class TranslationLanguagesContextMenuContent: ContextControllerIte
self.addSubnode(self.titleLabelNode)
self.highligthedChanged = { [weak self] highlighted in
guard let strongSelf = self else {
guard let strongSelf = self, let language = strongSelf.language, !language.isEmpty else {
return
}
if highlighted {
@ -461,6 +463,9 @@ private final class TranslationLanguagesContextMenuContent: ContextControllerIte
}
@objc private func pressed() {
guard let language = self.language, !language.isEmpty else {
return
}
self.action()
}
@ -476,7 +481,6 @@ private final class TranslationLanguagesContextMenuContent: ContextControllerIte
}
self.highlightBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
self.separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
self.highlightBackgroundNode.frame = CGRect(origin: CGPoint(), size: size)
@ -487,8 +491,15 @@ private final class TranslationLanguagesContextMenuContent: ContextControllerIte
let titleFrame = CGRect(origin: CGPoint(x: sideInset, y: floor((size.height - titleSize.height) / 2.0)), size: titleSize)
self.titleLabelNode.frame = titleFrame
self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: size.height), size: CGSize(width: size.width, height: UIScreenPixel))
self.separatorNode.isHidden = isLast
if language == "" {
self.separatorNode.backgroundColor = presentationData.theme.contextMenu.sectionSeparatorColor
self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: separatorHeight))
self.separatorNode.isHidden = false
} else {
self.separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: size.height), size: CGSize(width: size.width, height: UIScreenPixel))
self.separatorNode.isHidden = isLast
}
}
}
@ -537,26 +548,6 @@ private final class TranslationLanguagesContextMenuContent: ContextControllerIte
self.scrollNode.view.delegate = self
self.clipsToBounds = true
// self.stateDisposable = (self.listContext.state
// |> deliverOnMainQueue).start(next: { [weak self] state in
// guard let strongSelf = self else {
// return
// }
// let updatedState = ItemsState(listState: state, readStats: strongSelf.state.readStats)
// var animateIn = false
// if strongSelf.state.item(at: 0) == nil && updatedState.item(at: 0) != nil {
// animateIn = true
// }
// strongSelf.state = updatedState
// strongSelf.animateIn = true
// strongSelf.requestUpdate(strongSelf, animateIn ? .animated(duration: 0.2, curve: .easeInOut) : .immediate)
// if animateIn {
// for (_, itemNode) in strongSelf.itemNodes {
// itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
// }
// }
// })
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
@ -591,11 +582,23 @@ private final class TranslationLanguagesContextMenuContent: ContextControllerIte
let minVisibleIndex = max(0, Int(floor(visibleBounds.minY / itemHeight)))
let maxVisibleIndex = Int(ceil(visibleBounds.maxY / itemHeight))
var separatorIndex = 0
for i in 0 ..< self.languages.count {
if self.languages[i].0.isEmpty {
separatorIndex = i
break
}
}
if minVisibleIndex <= maxVisibleIndex {
for index in minVisibleIndex ... maxVisibleIndex {
let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: CGFloat(index) * itemHeight), size: CGSize(width: size.width, height: itemHeight))
let height = self.languages[index].0.isEmpty ? separatorHeight : itemHeight
var itemFrame = CGRect(origin: CGPoint(x: 0.0, y: CGFloat(index) * itemHeight), size: CGSize(width: size.width, height: height))
if index > separatorIndex {
itemFrame.origin.y += separatorHeight - itemHeight
}
if index < self.languages.count {
let (languageCode, displayTitle) = self.languages[index]
validIds.insert(index)
@ -612,7 +615,7 @@ private final class TranslationLanguagesContextMenuContent: ContextControllerIte
self.scrollNode.addSubnode(itemNode)
}
itemNode.update(size: itemFrame.size, presentationData: presentationData, language: languageCode, displayTitle: displayTitle, isLast: index == self.languages.count - 1, syncronousLoad: syncronousLoad)
itemNode.update(size: itemFrame.size, presentationData: presentationData, language: languageCode, displayTitle: displayTitle, isLast: index == self.languages.count - 1 || index == separatorIndex - 1, syncronousLoad: syncronousLoad)
itemNode.frame = itemFrame
}
}
@ -745,7 +748,7 @@ private final class TranslationLanguagesContextMenuContent: ContextControllerIte
topContentHeight += backButtonFrame.height
}
if let separatorNode = self.separatorNode {
let separatorFrame = CGRect(origin: CGPoint(x: 0.0, y: topContentHeight), size: CGSize(width: constrainedSize.width, height: 7.0))
let separatorFrame = CGRect(origin: CGPoint(x: 0.0, y: topContentHeight), size: CGSize(width: constrainedSize.width, height: separatorHeight))
separatorNode.backgroundColor = self.presentationData.theme.contextMenu.sectionSeparatorColor
transition.updateFrame(node: separatorNode, frame: separatorFrame)
topContentHeight += separatorFrame.height

View File

@ -10,6 +10,7 @@ import MergeLists
import AccountContext
import ChatPresentationInterfaceState
import ChatControllerInteraction
import ItemListUI
private struct CommandChatInputContextPanelEntryStableId: Hashable {
let command: PeerCommand
@ -36,8 +37,8 @@ private struct CommandChatInputContextPanelEntry: Comparable, Identifiable {
return lhs.index < rhs.index
}
func item(context: AccountContext, fontSize: PresentationFontSize, commandSelected: @escaping (PeerCommand, Bool) -> Void) -> ListViewItem {
return CommandChatInputPanelItem(context: context, theme: self.theme, fontSize: fontSize, command: self.command, commandSelected: commandSelected)
func item(context: AccountContext, presentationData: PresentationData, commandSelected: @escaping (PeerCommand, Bool) -> Void) -> ListViewItem {
return CommandChatInputPanelItem(context: context, presentationData: ItemListPresentationData(presentationData), command: self.command, commandSelected: commandSelected)
}
}
@ -47,12 +48,12 @@ private struct CommandChatInputContextPanelTransition {
let updates: [ListViewUpdateItem]
}
private func preparedTransition(from fromEntries: [CommandChatInputContextPanelEntry], to toEntries: [CommandChatInputContextPanelEntry], context: AccountContext, fontSize: PresentationFontSize, commandSelected: @escaping (PeerCommand, Bool) -> Void) -> CommandChatInputContextPanelTransition {
private func preparedTransition(from fromEntries: [CommandChatInputContextPanelEntry], to toEntries: [CommandChatInputContextPanelEntry], context: AccountContext, presentationData: PresentationData, commandSelected: @escaping (PeerCommand, Bool) -> Void) -> CommandChatInputContextPanelTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, fontSize: fontSize, commandSelected: commandSelected), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, fontSize: fontSize, commandSelected: commandSelected), directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, commandSelected: commandSelected), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, commandSelected: commandSelected), directionHint: nil) }
return CommandChatInputContextPanelTransition(deletions: deletions, insertions: insertions, updates: updates)
}
@ -101,7 +102,8 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode {
private func prepareTransition(from: [CommandChatInputContextPanelEntry]? , to: [CommandChatInputContextPanelEntry]) {
let firstTime = self.currentEntries == nil
let transition = preparedTransition(from: from ?? [], to: to, context: self.context, fontSize: self.fontSize, commandSelected: { [weak self] command, sendImmediately in
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let transition = preparedTransition(from: from ?? [], to: to, context: self.context, presentationData: presentationData, commandSelected: { [weak self] command, sendImmediately in
if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction {
if sendImmediately {
interfaceInteraction.sendBotCommand(command.peer, "/" + command.command.text)

Some files were not shown because too many files have changed in this diff Show More