mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
Merge commit 'f80093608077ec9987c39fbe9a333412dd85dfa5'
This commit is contained in:
commit
b876a5c829
@ -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>
|
||||
|
@ -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";
|
||||
|
@ -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]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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()
|
||||
|
@ -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() {
|
||||
|
@ -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)
|
||||
|
@ -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, _, _, _, _):
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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?()
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}];
|
||||
|
@ -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];
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -128,6 +128,9 @@ final class PasscodeEntryButtonNode: HighlightTrackingButtonNode {
|
||||
|
||||
super.init()
|
||||
|
||||
self.accessibilityLabel = title
|
||||
self.accessibilityTraits = .keyboardKey
|
||||
|
||||
if let gradientBackgroundNode = self.gradientBackgroundNode {
|
||||
self.addSubnode(gradientBackgroundNode)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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 {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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?
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)?
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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?
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user