Merge branch 'unlockable-media'

This commit is contained in:
Ilya Laktyushin 2022-08-31 18:18:44 +02:00
commit 39374476bb
32 changed files with 791 additions and 142 deletions

View File

@ -466,6 +466,7 @@ associated_domains_fragment = "" if telegram_bundle_id not in official_bundle_id
<array>
<string>applinks:telegram.me</string>
<string>applinks:t.me</string>
<string>applinks:*.t.me</string>
</array>
"""

View File

@ -7999,6 +7999,7 @@ Sorry for the inconvenience.";
"Login.WrongCodeError" = "Wrong code, please try again.";
"PrivacySettings.LoginEmail" = "Login Email";
"PrivacySettings.LoginEmailInfo" = "Change your email address for Telegram login codes.";
"Login.EmailChanged" = "Your email has been changed.";
"Login.InvalidEmailAddressError" = "An error occurred. Please try again.";

View File

@ -338,47 +338,22 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
),
prefix: codePrefix,
count: codeLength,
width: layout.size.width - 28.0
width: layout.size.width - 28.0,
compact: layout.size.width <= 320.0
)
var items: [AuthorizationLayoutItem] = []
items.append(AuthorizationLayoutItem(node: self.animationNode, size: animationSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 10.0, maxValue: 10.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
self.animationNode.updateLayout(size: animationSize)
if layout.size.width > 320.0 {
items.append(AuthorizationLayoutItem(node: self.animationNode, size: animationSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 10.0, maxValue: 10.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
self.animationNode.updateLayout(size: animationSize)
} else {
insets.top = navigationBarHeight
}
var additionalBottomInset: CGFloat = 20.0
if let codeType = self.codeType {
switch codeType {
case .otherSession:
self.titleIconNode.isHidden = false
if self.titleIconNode.image == nil {
self.titleIconNode.image = generateImage(CGSize(width: 81.0, height: 52.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(theme.list.itemPrimaryTextColor.cgColor)
context.setStrokeColor(theme.list.itemPrimaryTextColor.cgColor)
context.setLineWidth(2.97)
let _ = try? drawSvgPath(context, path: "M9.87179487,9.04664384 C9.05602951,9.04664384 8.39525641,9.70682916 8.39525641,10.5205479 L8.39525641,44.0547945 C8.39525641,44.8685133 9.05602951,45.5286986 9.87179487,45.5286986 L65.1538462,45.5286986 C65.9696115,45.5286986 66.6303846,44.8685133 66.6303846,44.0547945 L66.6303846,10.5205479 C66.6303846,9.70682916 65.9696115,9.04664384 65.1538462,9.04664384 L9.87179487,9.04664384 S ")
let _ = try? drawSvgPath(context, path: "M0,44.0547945 L75.025641,44.0547945 C75.025641,45.2017789 74.2153348,46.1893143 73.0896228,46.4142565 L66.1123641,47.8084669 C65.4749109,47.9358442 64.8264231,48 64.1763458,48 L10.8492952,48 C10.1992179,48 9.55073017,47.9358442 8.91327694,47.8084669 L1.93601826,46.4142565 C0.810306176,46.1893143 0,45.2017789 0,44.0547945 Z ")
let _ = try? drawSvgPath(context, path: "M2.96153846,16.4383562 L14.1495726,16.4383562 C15.7851852,16.4383562 17.1111111,17.7631027 17.1111111,19.3972603 L17.1111111,45.0410959 C17.1111111,46.6752535 15.7851852,48 14.1495726,48 L2.96153846,48 C1.32592593,48 0,46.6752535 0,45.0410959 L0,19.3972603 C0,17.7631027 1.32592593,16.4383562 2.96153846,16.4383562 Z ")
context.setStrokeColor(theme.list.plainBackgroundColor.cgColor)
context.setLineWidth(1.65)
let _ = try? drawSvgPath(context, path: "M2.96153846,15.6133562 L14.1495726,15.6133562 C16.2406558,15.6133562 17.9361111,17.3073033 17.9361111,19.3972603 L17.9361111,45.0410959 C17.9361111,47.1310529 16.2406558,48.825 14.1495726,48.825 L2.96153846,48.825 C0.870455286,48.825 -0.825,47.1310529 -0.825,45.0410959 L-0.825,19.3972603 C-0.825,17.3073033 0.870455286,15.6133562 2.96153846,15.6133562 S ")
context.setFillColor(theme.list.plainBackgroundColor.cgColor)
let _ = try? drawSvgPath(context, path: "M1.64529915,20.3835616 L15.465812,20.3835616 L15.465812,44.0547945 L1.64529915,44.0547945 Z ")
context.setFillColor(theme.list.itemAccentColor.cgColor)
let _ = try? drawSvgPath(context, path: "M66.4700855,0.0285884455 C60.7084674,0.0285884455 55.9687848,4.08259697 55.9687848,9.14830256 C55.9687848,12.0875991 57.5993165,14.6795278 60.0605723,16.3382966 C60.0568181,16.4358994 60.0611217,16.5884309 59.9318097,17.067302 C59.7721478,17.6586615 59.4575977,18.4958519 58.8015608,19.4258487 L58.3294314,20.083383 L59.1449275,20.0976772 C61.9723538,20.1099725 63.6110772,18.2528913 63.8662207,17.9535438 C64.7014993,18.1388449 65.5698144,18.2680167 66.4700855,18.2680167 C72.2312622,18.2680167 76.9713861,14.2140351 76.9713861,9.14830256 C76.9713861,4.08256999 72.2312622,0.0285884455 66.4700855,0.0285884455 Z ")
let _ = try? drawSvgPath(context, path: "M64.1551769,18.856071 C63.8258967,19.1859287 63.4214479,19.5187 62.9094963,19.840779 C61.8188563,20.5269227 60.5584776,20.9288319 59.1304689,20.9225505 L56.7413094,20.8806727 L57.6592902,19.6022014 L58.127415,18.9502938 C58.6361919,18.2290526 58.9525079,17.5293964 59.1353377,16.8522267 C59.1487516,16.8025521 59.1603548,16.7584153 59.1703974,16.7187893 C56.653362,14.849536 55.1437848,12.1128655 55.1437848,9.14830256 C55.1437848,3.61947515 60.2526259,-0.796411554 66.4700855,-0.796411554 C72.6872626,-0.796411554 77.7963861,3.61958236 77.7963861,9.14830256 C77.7963861,14.6770228 72.6872626,19.0930167 66.4700855,19.0930167 C65.7185957,19.0930167 64.9627196,19.0118067 64.1551769,18.856071 S ")
})
}
// items.append(AuthorizationLayoutItem(node: self.titleIconNode, size: self.titleIconNode.image!.size, spacingBefore: AuthorizationLayoutItemSpacing(weight: 41.0, maxValue: 41.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
items.append(AuthorizationLayoutItem(node: self.titleNode, size: titleSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
items.append(AuthorizationLayoutItem(node: self.currentOptionNode, size: currentOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 10.0, maxValue: 10.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
@ -410,14 +385,11 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
items.append(AuthorizationLayoutItem(node: self.nextOptionButtonNode, size: nextOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 50.0, maxValue: 120.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
default:
self.titleIconNode.isHidden = true
items.append(AuthorizationLayoutItem(node: self.titleNode, size: titleSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
items.append(AuthorizationLayoutItem(node: self.currentOptionNode, size: currentOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
items.append(AuthorizationLayoutItem(node: self.codeInputView, size: codeFieldSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 30.0, maxValue: 30.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 104.0, maxValue: 104.0)))
/*items.append(AuthorizationLayoutItem(node: self.codeField, size: CGSize(width: layout.size.width - 88.0, height: 44.0), spacingBefore: AuthorizationLayoutItemSpacing(weight: 40.0, maxValue: 100.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
items.append(AuthorizationLayoutItem(node: self.codeSeparatorNode, size: CGSize(width: layout.size.width - 88.0, height: UIScreenPixel), spacingBefore: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))*/
if self.appleSignInAllowed, let signInWithAppleButton = self.signInWithAppleButton {
additionalBottomInset = 80.0

View File

@ -172,11 +172,17 @@ final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UIText
private func updateButtonsVisibility(transition: ContainedViewLayoutTransition) {
if self.currentEmail.isEmpty && self.appleSignInAllowed {
transition.updateAlpha(node: self.proceedNode, alpha: 0.0)
if self.proceedNode.isHidden {
transition.updateAlpha(node: self.dividerNode, alpha: 1.0)
}
if let signInWithAppleButton = self.signInWithAppleButton {
transition.updateAlpha(layer: signInWithAppleButton.layer, alpha: 1.0)
}
} else {
transition.updateAlpha(node: self.proceedNode, alpha: 1.0)
if self.proceedNode.isHidden {
transition.updateAlpha(node: self.dividerNode, alpha: 0.0)
}
if let signInWithAppleButton = self.signInWithAppleButton {
transition.updateAlpha(layer: signInWithAppleButton.layer, alpha: 0.0)
}
@ -216,7 +222,7 @@ final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UIText
items.append(AuthorizationLayoutItem(node: self.titleNode, size: titleSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: titleInset, maxValue: titleInset), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
items.append(AuthorizationLayoutItem(node: self.noticeNode, size: noticeSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
items.append(AuthorizationLayoutItem(node: self.codeField, size: CGSize(width: layout.size.width - 88.0, height: 44.0), spacingBefore: AuthorizationLayoutItemSpacing(weight: 22.0, maxValue: 40.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
items.append(AuthorizationLayoutItem(node: self.codeField, size: CGSize(width: layout.size.width - 88.0, height: 44.0), spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 30.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
items.append(AuthorizationLayoutItem(node: self.codeSeparatorNode, size: CGSize(width: layout.size.width - 48.0, height: UIScreenPixel), spacingBefore: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
if layout.size.width > 320.0 {
@ -244,7 +250,7 @@ final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UIText
self.dividerNode.isHidden = true
}
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 - 80.0)), items: items, transition: transition, failIfDoesNotFit: false)
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 - 110.0)), items: items, transition: transition, failIfDoesNotFit: false)
if let signInWithAppleButton = self.signInWithAppleButton, self.appleSignInAllowed {
signInWithAppleButton.isHidden = false

View File

@ -907,7 +907,7 @@ final class PhoneConfirmationController: ViewController {
self.codeTargetNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false)
self.codeTargetNode.layer.animatePosition(from: self.codeTargetNode.position, to: self.codeSourceNode.position, duration: duration)
Queue.mainQueue().after(0.23) {
Queue.mainQueue().after(0.2) {
codeNode.isHidden = false
numberNode.isHidden = false
buttonNode.isHidden = false

View File

@ -121,6 +121,7 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
private var theme: Theme?
private var count: Int?
private var prefix: String = ""
private var compact = false
private var textValue: String = ""
public var text: String {
@ -261,7 +262,7 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
let fontSize: CGFloat
if self.prefix.isEmpty {
let height: CGFloat = 51.0
let height: CGFloat = self.compact ? 44.0 : 51.0
fontSize = floor(13.0 * height / 28.0)
} else {
let height: CGFloat = 28.0
@ -291,10 +292,11 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
}
}
public func update(theme: Theme, prefix: String, count: Int, width: CGFloat) -> CGSize {
public func update(theme: Theme, prefix: String, count: Int, width: CGFloat, compact: Bool) -> CGSize {
self.theme = theme
self.count = count
self.prefix = prefix
self.compact = compact
if theme.isDark {
self.textField.keyboardAppearance = .dark
@ -305,7 +307,7 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
let fontSize: CGFloat
let height: CGFloat
if prefix.isEmpty {
height = 51.0
height = compact ? 44.0 : 51.0
fontSize = floor(13.0 * height / 28.0)
} else {
height = 28.0

View File

@ -109,7 +109,10 @@ public func chatMessageGalleryControllerData(context: AccountContext, chatLocati
}
}
for media in message.media {
if let action = media as? TelegramMediaAction {
if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia, case let .full(fullMedia) = extendedMedia {
standalone = true
galleryMedia = fullMedia
} else if let action = media as? TelegramMediaAction {
switch action.action {
case let .photoUpdated(image):
if let peer = messageMainPeer(EngineMessage(message)), let image = image {

View File

@ -43,7 +43,9 @@ private func tagsForMessage(_ message: Message) -> MessageTags? {
}
private func galleryMediaForMedia(media: Media) -> Media? {
if let media = media as? TelegramMediaImage {
if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia, case let .full(fullMedia) = extendedMedia {
return fullMedia
} else if let media = media as? TelegramMediaImage {
return media
} else if let file = media as? TelegramMediaFile {
if file.mimeType.hasPrefix("audio/") {

View File

@ -130,7 +130,9 @@ class ChatImageGalleryItem: GalleryItem {
node.setMessage(self.message, displayInfo: !self.displayInfoOnTop)
for media in self.message.media {
if let image = media as? TelegramMediaImage {
if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia, case let .full(fullMedia) = extendedMedia, let image = fullMedia as? TelegramMediaImage {
node.setImage(imageReference: .message(message: MessageReference(self.message), media: image))
} else if let image = media as? TelegramMediaImage {
node.setImage(imageReference: .message(message: MessageReference(self.message), media: image))
break
} else if let file = media as? TelegramMediaFile, file.mimeType.hasPrefix("image/") {

View File

@ -256,6 +256,8 @@ public func chatMessagePhotoDatas(postbox: Postbox, photoReference: ImageMediaRe
})
return signal
} else if let decodedThumbnailData = photoReference.media.immediateThumbnailData.flatMap(decodeTinyThumbnail) {
return .single(Tuple(decodedThumbnailData, nil, .blurred, false))
} else {
return .never()
}

View File

@ -114,14 +114,18 @@ class EmojiHeaderComponent: Component {
let initialPosition = self.statusView.center
let targetPosition = self.statusView.superview!.convert(self.statusView.center, to: containerView)
let sourcePosition = animateFrom.superview!.convert(animateFrom.center, to: containerView).offsetBy(dx: 0.0, dy: -20.0)
let sourcePosition = animateFrom.superview!.convert(animateFrom.center, to: containerView).offsetBy(dx: 0.0, dy: 0.0)
containerView.addSubview(self.statusView)
self.statusView.center = targetPosition
animateFrom.alpha = 0.0
self.statusView.layer.animateScale(from: 0.05, to: 1.0, duration: 0.55, timingFunction: kCAMediaTimingFunctionSpring)
self.statusView.layer.animatePosition(from: sourcePosition, to: targetPosition, duration: 0.55, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in
self.statusView.layer.animateScale(from: 0.24, to: 1.0, duration: 0.36, timingFunction: CAMediaTimingFunctionName.linear.rawValue)
let transition = ContainedViewLayoutTransition.animated(duration: 0.36, curve: .linear)
transition.animatePositionWithKeyframes(layer: self.statusView.layer, keyframes: generateParabollicMotionKeyframes(from: sourcePosition, to: targetPosition, elevation: 50.0))
Queue.mainQueue().after(0.55, {
self.addSubview(self.statusView)
self.statusView.center = initialPosition
})
@ -170,3 +174,37 @@ class EmojiHeaderComponent: Component {
return view.update(component: self, availableSize: availableSize, transition: transition)
}
}
private func generateParabollicMotionKeyframes(from sourcePoint: CGPoint, to targetPosition: CGPoint, elevation: CGFloat) -> [CGPoint] {
let midPoint = CGPoint(x: (sourcePoint.x + targetPosition.x) / 2.0, y: sourcePoint.y - elevation)
let x1 = sourcePoint.x
let y1 = sourcePoint.y
let x2 = midPoint.x
let y2 = midPoint.y
let x3 = targetPosition.x
let y3 = targetPosition.y
var keyframes: [CGPoint] = []
if abs(y1 - y3) < 5.0 && abs(x1 - x3) < 5.0 {
for i in 0 ..< 10 {
let k = CGFloat(i) / CGFloat(10 - 1)
let x = sourcePoint.x * (1.0 - k) + targetPosition.x * k
let y = sourcePoint.y * (1.0 - k) + targetPosition.y * k
keyframes.append(CGPoint(x: x, y: y))
}
} else {
let a = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
let b = (x1 * x1 * (y2 - y3) + x3 * x3 * (y1 - y2) + x2 * x2 * (y3 - y1)) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
let c = (x2 * x2 * (x3 * y1 - x1 * y3) + x2 * (x1 * x1 * y3 - x3 * x3 * y1) + x1 * x3 * (x3 - x1) * y2) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
for i in 0 ..< 10 {
let k = CGFloat(i) / CGFloat(10 - 1)
let x = sourcePoint.x * (1.0 - k) + targetPosition.x * k
let y = a * x * x + b * x + c
keyframes.append(CGPoint(x: x, y: y))
}
}
return keyframes
}

View File

@ -92,6 +92,7 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
case passcode(PresentationTheme, String, Bool, String)
case twoStepVerification(PresentationTheme, String, String, TwoStepVerificationAccessConfiguration?)
case loginEmail(PresentationTheme, String, String?)
case loginEmailInfo(PresentationTheme, String)
case activeSessions(PresentationTheme, String, String)
case autoArchiveHeader(String)
case autoArchive(String, Bool)
@ -104,7 +105,7 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
var section: ItemListSectionId {
switch self {
case .blockedPeers, .activeSessions, .passcode, .twoStepVerification, .loginEmail:
case .blockedPeers, .activeSessions, .passcode, .twoStepVerification, .loginEmail, .loginEmailInfo:
return PrivacyAndSecuritySection.general.rawValue
case .privacyHeader, .phoneNumberPrivacy, .lastSeenPrivacy, .profilePhotoPrivacy, .forwardPrivacy, .groupPrivacy, .voiceCallPrivacy, .voiceMessagePrivacy, .selectivePrivacyInfo:
return PrivacyAndSecuritySection.privacy.rawValue
@ -129,40 +130,42 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
return 4
case .loginEmail:
return 5
case .privacyHeader:
case .loginEmailInfo:
return 6
case .phoneNumberPrivacy:
case .privacyHeader:
return 7
case .lastSeenPrivacy:
case .phoneNumberPrivacy:
return 8
case .profilePhotoPrivacy:
case .lastSeenPrivacy:
return 9
case .voiceCallPrivacy:
case .profilePhotoPrivacy:
return 10
case .voiceMessagePrivacy:
case .voiceCallPrivacy:
return 11
case .forwardPrivacy:
case .voiceMessagePrivacy:
return 12
case .groupPrivacy:
case .forwardPrivacy:
return 13
case .selectivePrivacyInfo:
case .groupPrivacy:
return 14
case .autoArchiveHeader:
case .selectivePrivacyInfo:
return 15
case .autoArchive:
case .autoArchiveHeader:
return 16
case .autoArchiveInfo:
case .autoArchive:
return 17
case .accountHeader:
case .autoArchiveInfo:
return 18
case .accountTimeout:
case .accountHeader:
return 19
case .accountInfo:
case .accountTimeout:
return 20
case .dataSettings:
case .accountInfo:
return 21
case .dataSettingsInfo:
case .dataSettings:
return 22
case .dataSettingsInfo:
return 23
}
}
@ -246,6 +249,12 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
} else {
return false
}
case let .loginEmailInfo(lhsTheme, lhsText):
if case let .loginEmailInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .activeSessions(lhsTheme, lhsText, lhsValue):
if case let .activeSessions(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
@ -358,6 +367,8 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/LoginEmail")?.precomposed(), title: text, label: "", sectionId: self.section, style: .blocks, action: {
arguments.openEmailSettings(emailPattern)
})
case let .loginEmailInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .activeSessions(_, text, value):
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Websites")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
arguments.openActiveSessions()
@ -474,7 +485,8 @@ private func privacyAndSecurityControllerEntries(
entries.append(.twoStepVerification(presentationData.theme, presentationData.strings.PrivacySettings_TwoStepAuth, twoStepAuthString, twoStepAuthData))
if loginEmail != nil {
entries.append(.loginEmail(presentationData.theme, presentationData.strings.PrivacySettings_LoginEmail, loginEmail))
entries.append(.loginEmail(presentationData.theme, presentationData.strings.PrivacySettings_LoginEmail, loginEmail))
entries.append(.loginEmailInfo(presentationData.theme, presentationData.strings.PrivacySettings_LoginEmailInfo))
}
entries.append(.privacyHeader(presentationData.theme, presentationData.strings.PrivacySettings_PrivacyTitle))

View File

@ -729,8 +729,19 @@ private final class StickerPackContainer: ASDisplayNode {
case .unknown, .none:
backgroundAlpha = 1.0
}
self.actionAreaBackgroundNode.alpha = backgroundAlpha
self.actionAreaSeparatorNode.alpha = backgroundAlpha
let transition: ContainedViewLayoutTransition
var delay: Double = 0.0
if backgroundAlpha >= self.actionAreaBackgroundNode.alpha || abs(backgroundAlpha - self.actionAreaBackgroundNode.alpha) < 0.01 {
transition = .immediate
} else {
transition = .animated(duration: 0.2, curve: .linear)
if abs(backgroundAlpha - self.actionAreaBackgroundNode.alpha) > 0.9 {
delay = 0.2
}
}
transition.updateAlpha(node: self.actionAreaBackgroundNode, alpha: backgroundAlpha, delay: delay)
transition.updateAlpha(node: self.actionAreaSeparatorNode, alpha: backgroundAlpha, delay: delay)
}
private func updateStickerPackContents(_ contents: [LoadedStickerPack], hasPremium: Bool) {

View File

@ -300,7 +300,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-750828557] = { return Api.InputMedia.parse_inputMediaGame($0) }
dict[-1759532989] = { return Api.InputMedia.parse_inputMediaGeoLive($0) }
dict[-104578748] = { return Api.InputMedia.parse_inputMediaGeoPoint($0) }
dict[-646342540] = { return Api.InputMedia.parse_inputMediaInvoice($0) }
dict[-1900697899] = { return Api.InputMedia.parse_inputMediaInvoice($0) }
dict[-1279654347] = { return Api.InputMedia.parse_inputMediaPhoto($0) }
dict[-440664550] = { return Api.InputMedia.parse_inputMediaPhotoExternal($0) }
dict[261416433] = { return Api.InputMedia.parse_inputMediaPoll($0) }
@ -354,7 +354,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[42402760] = { return Api.InputStickerSet.parse_inputStickerSetAnimatedEmoji($0) }
dict[215889721] = { return Api.InputStickerSet.parse_inputStickerSetAnimatedEmojiAnimations($0) }
dict[-427863538] = { return Api.InputStickerSet.parse_inputStickerSetDice($0) }
dict[701560302] = { return Api.InputStickerSet.parse_inputStickerSetEmojiDefaultStatuses($0) }
dict[80008398] = { return Api.InputStickerSet.parse_inputStickerSetEmojiGenericAnimations($0) }
dict[-4838507] = { return Api.InputStickerSet.parse_inputStickerSetEmpty($0) }
dict[-1645763991] = { return Api.InputStickerSet.parse_inputStickerSetID($0) }
@ -467,6 +466,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1672577397] = { return Api.MessageEntity.parse_messageEntityUnderline($0) }
dict[-1148011883] = { return Api.MessageEntity.parse_messageEntityUnknown($0) }
dict[1859134776] = { return Api.MessageEntity.parse_messageEntityUrl($0) }
dict[-297296796] = { return Api.MessageExtendedMedia.parse_messageExtendedMedia($0) }
dict[-1386050360] = { return Api.MessageExtendedMedia.parse_messageExtendedMediaPreview($0) }
dict[1601666510] = { return Api.MessageFwdHeader.parse_messageFwdHeader($0) }
dict[-1387279939] = { return Api.MessageInteractionCounters.parse_messageInteractionCounters($0) }
dict[1882335561] = { return Api.MessageMedia.parse_messageMediaContact($0) }
@ -476,7 +477,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-38694904] = { return Api.MessageMedia.parse_messageMediaGame($0) }
dict[1457575028] = { return Api.MessageMedia.parse_messageMediaGeo($0) }
dict[-1186937242] = { return Api.MessageMedia.parse_messageMediaGeoLive($0) }
dict[-2074799289] = { return Api.MessageMedia.parse_messageMediaInvoice($0) }
dict[-156940077] = { return Api.MessageMedia.parse_messageMediaInvoice($0) }
dict[1766936791] = { return Api.MessageMedia.parse_messageMediaPhoto($0) }
dict[1272375192] = { return Api.MessageMedia.parse_messageMediaPoll($0) }
dict[-1618676578] = { return Api.MessageMedia.parse_messageMediaUnsupported($0) }
@ -799,6 +800,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1442983757] = { return Api.Update.parse_updateLangPack($0) }
dict[1180041828] = { return Api.Update.parse_updateLangPackTooLong($0) }
dict[1448076945] = { return Api.Update.parse_updateLoginToken($0) }
dict[1517529484] = { return Api.Update.parse_updateMessageExtendedMedia($0) }
dict[1318109142] = { return Api.Update.parse_updateMessageID($0) }
dict[-1398708869] = { return Api.Update.parse_updateMessagePoll($0) }
dict[274961865] = { return Api.Update.parse_updateMessagePollVote($0) }
@ -1410,6 +1412,8 @@ public extension Api {
_1.serialize(buffer, boxed)
case let _1 as Api.MessageEntity:
_1.serialize(buffer, boxed)
case let _1 as Api.MessageExtendedMedia:
_1.serialize(buffer, boxed)
case let _1 as Api.MessageFwdHeader:
_1.serialize(buffer, boxed)
case let _1 as Api.MessageInteractionCounters:

View File

@ -540,6 +540,82 @@ public extension Api {
}
}
public extension Api {
enum MessageExtendedMedia: TypeConstructorDescription {
case messageExtendedMedia(media: Api.MessageMedia)
case messageExtendedMediaPreview(flags: Int32, w: Int32?, h: Int32?, thumb: Api.PhotoSize?, videoDuration: Int32?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .messageExtendedMedia(let media):
if boxed {
buffer.appendInt32(-297296796)
}
media.serialize(buffer, true)
break
case .messageExtendedMediaPreview(let flags, let w, let h, let thumb, let videoDuration):
if boxed {
buffer.appendInt32(-1386050360)
}
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(w!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(h!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {thumb!.serialize(buffer, true)}
if Int(flags) & Int(1 << 2) != 0 {serializeInt32(videoDuration!, buffer: buffer, boxed: false)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .messageExtendedMedia(let media):
return ("messageExtendedMedia", [("media", String(describing: media))])
case .messageExtendedMediaPreview(let flags, let w, let h, let thumb, let videoDuration):
return ("messageExtendedMediaPreview", [("flags", String(describing: flags)), ("w", String(describing: w)), ("h", String(describing: h)), ("thumb", String(describing: thumb)), ("videoDuration", String(describing: videoDuration))])
}
}
public static func parse_messageExtendedMedia(_ reader: BufferReader) -> MessageExtendedMedia? {
var _1: Api.MessageMedia?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.MessageMedia
}
let _c1 = _1 != nil
if _c1 {
return Api.MessageExtendedMedia.messageExtendedMedia(media: _1!)
}
else {
return nil
}
}
public static func parse_messageExtendedMediaPreview(_ reader: BufferReader) -> MessageExtendedMedia? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() }
var _3: Int32?
if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() }
var _4: Api.PhotoSize?
if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.PhotoSize
} }
var _5: Int32?
if Int(_1!) & Int(1 << 2) != 0 {_5 = reader.readInt32() }
let _c1 = _1 != nil
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil
let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.MessageExtendedMedia.messageExtendedMediaPreview(flags: _1!, w: _2, h: _3, thumb: _4, videoDuration: _5)
}
else {
return nil
}
}
}
}
public extension Api {
enum MessageFwdHeader: TypeConstructorDescription {
case messageFwdHeader(flags: Int32, fromId: Api.Peer?, fromName: String?, date: Int32, channelPost: Int32?, postAuthor: String?, savedFromPeer: Api.Peer?, savedFromMsgId: Int32?, psaType: String?)
@ -657,7 +733,7 @@ public extension Api {
}
}
public extension Api {
enum MessageMedia: TypeConstructorDescription {
indirect enum MessageMedia: TypeConstructorDescription {
case messageMediaContact(phoneNumber: String, firstName: String, lastName: String, vcard: String, userId: Int64)
case messageMediaDice(value: Int32, emoticon: String)
case messageMediaDocument(flags: Int32, document: Api.Document?, ttlSeconds: Int32?)
@ -665,7 +741,7 @@ public extension Api {
case messageMediaGame(game: Api.Game)
case messageMediaGeo(geo: Api.GeoPoint)
case messageMediaGeoLive(flags: Int32, geo: Api.GeoPoint, heading: Int32?, period: Int32, proximityNotificationRadius: Int32?)
case messageMediaInvoice(flags: Int32, title: String, description: String, photo: Api.WebDocument?, receiptMsgId: Int32?, currency: String, totalAmount: Int64, startParam: String)
case messageMediaInvoice(flags: Int32, title: String, description: String, photo: Api.WebDocument?, receiptMsgId: Int32?, currency: String, totalAmount: Int64, startParam: String, extendedMedia: Api.MessageExtendedMedia?)
case messageMediaPhoto(flags: Int32, photo: Api.Photo?, ttlSeconds: Int32?)
case messageMediaPoll(poll: Api.Poll, results: Api.PollResults)
case messageMediaUnsupported
@ -727,9 +803,9 @@ public extension Api {
serializeInt32(period, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(proximityNotificationRadius!, buffer: buffer, boxed: false)}
break
case .messageMediaInvoice(let flags, let title, let description, let photo, let receiptMsgId, let currency, let totalAmount, let startParam):
case .messageMediaInvoice(let flags, let title, let description, let photo, let receiptMsgId, let currency, let totalAmount, let startParam, let extendedMedia):
if boxed {
buffer.appendInt32(-2074799289)
buffer.appendInt32(-156940077)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(title, buffer: buffer, boxed: false)
@ -739,6 +815,7 @@ public extension Api {
serializeString(currency, buffer: buffer, boxed: false)
serializeInt64(totalAmount, buffer: buffer, boxed: false)
serializeString(startParam, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 4) != 0 {extendedMedia!.serialize(buffer, true)}
break
case .messageMediaPhoto(let flags, let photo, let ttlSeconds):
if boxed {
@ -797,8 +874,8 @@ public extension Api {
return ("messageMediaGeo", [("geo", String(describing: geo))])
case .messageMediaGeoLive(let flags, let geo, let heading, let period, let proximityNotificationRadius):
return ("messageMediaGeoLive", [("flags", String(describing: flags)), ("geo", String(describing: geo)), ("heading", String(describing: heading)), ("period", String(describing: period)), ("proximityNotificationRadius", String(describing: proximityNotificationRadius))])
case .messageMediaInvoice(let flags, let title, let description, let photo, let receiptMsgId, let currency, let totalAmount, let startParam):
return ("messageMediaInvoice", [("flags", String(describing: flags)), ("title", String(describing: title)), ("description", String(describing: description)), ("photo", String(describing: photo)), ("receiptMsgId", String(describing: receiptMsgId)), ("currency", String(describing: currency)), ("totalAmount", String(describing: totalAmount)), ("startParam", String(describing: startParam))])
case .messageMediaInvoice(let flags, let title, let description, let photo, let receiptMsgId, let currency, let totalAmount, let startParam, let extendedMedia):
return ("messageMediaInvoice", [("flags", String(describing: flags)), ("title", String(describing: title)), ("description", String(describing: description)), ("photo", String(describing: photo)), ("receiptMsgId", String(describing: receiptMsgId)), ("currency", String(describing: currency)), ("totalAmount", String(describing: totalAmount)), ("startParam", String(describing: startParam)), ("extendedMedia", String(describing: extendedMedia))])
case .messageMediaPhoto(let flags, let photo, let ttlSeconds):
return ("messageMediaPhoto", [("flags", String(describing: flags)), ("photo", String(describing: photo)), ("ttlSeconds", String(describing: ttlSeconds))])
case .messageMediaPoll(let poll, let results):
@ -941,6 +1018,10 @@ public extension Api {
_7 = reader.readInt64()
var _8: String?
_8 = parseString(reader)
var _9: Api.MessageExtendedMedia?
if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() {
_9 = Api.parse(reader, signature: signature) as? Api.MessageExtendedMedia
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
@ -949,8 +1030,9 @@ public extension Api {
let _c6 = _6 != nil
let _c7 = _7 != nil
let _c8 = _8 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
return Api.MessageMedia.messageMediaInvoice(flags: _1!, title: _2!, description: _3!, photo: _4, receiptMsgId: _5, currency: _6!, totalAmount: _7!, startParam: _8!)
let _c9 = (Int(_1!) & Int(1 << 4) == 0) || _9 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 {
return Api.MessageMedia.messageMediaInvoice(flags: _1!, title: _2!, description: _3!, photo: _4, receiptMsgId: _5, currency: _6!, totalAmount: _7!, startParam: _8!, extendedMedia: _9)
}
else {
return nil

View File

@ -608,6 +608,7 @@ public extension Api {
case updateLangPack(difference: Api.LangPackDifference)
case updateLangPackTooLong(langCode: String)
case updateLoginToken
case updateMessageExtendedMedia(peer: Api.Peer, msgId: Int32, extendedMedia: Api.MessageExtendedMedia)
case updateMessageID(id: Int32, randomId: Int64)
case updateMessagePoll(flags: Int32, pollId: Int64, poll: Api.Poll?, results: Api.PollResults)
case updateMessagePollVote(pollId: Int64, userId: Int64, options: [Buffer], qts: Int32)
@ -1148,6 +1149,14 @@ public extension Api {
buffer.appendInt32(1448076945)
}
break
case .updateMessageExtendedMedia(let peer, let msgId, let extendedMedia):
if boxed {
buffer.appendInt32(1517529484)
}
peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
extendedMedia.serialize(buffer, true)
break
case .updateMessageID(let id, let randomId):
if boxed {
@ -1679,6 +1688,8 @@ public extension Api {
return ("updateLangPackTooLong", [("langCode", String(describing: langCode))])
case .updateLoginToken:
return ("updateLoginToken", [])
case .updateMessageExtendedMedia(let peer, let msgId, let extendedMedia):
return ("updateMessageExtendedMedia", [("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("extendedMedia", String(describing: extendedMedia))])
case .updateMessageID(let id, let randomId):
return ("updateMessageID", [("id", String(describing: id)), ("randomId", String(describing: randomId))])
case .updateMessagePoll(let flags, let pollId, let poll, let results):
@ -2805,6 +2816,27 @@ public extension Api {
public static func parse_updateLoginToken(_ reader: BufferReader) -> Update? {
return Api.Update.updateLoginToken
}
public static func parse_updateMessageExtendedMedia(_ reader: BufferReader) -> Update? {
var _1: Api.Peer?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.Peer
}
var _2: Int32?
_2 = reader.readInt32()
var _3: Api.MessageExtendedMedia?
if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.MessageExtendedMedia
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.Update.updateMessageExtendedMedia(peer: _1!, msgId: _2!, extendedMedia: _3!)
}
else {
return nil
}
}
public static func parse_updateMessageID(_ reader: BufferReader) -> Update? {
var _1: Int32?
_1 = reader.readInt32()

View File

@ -4278,6 +4278,26 @@ public extension Api.functions.messages {
})
}
}
public extension Api.functions.messages {
static func getExtendedMedia(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(-2064119788)
peer.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(id.count))
for item in id {
serializeInt32(item, buffer: buffer, boxed: false)
}
return (FunctionDescription(name: "messages.getExtendedMedia", parameters: [("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
}
public extension Api.functions.messages {
static func getFavedStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.FavedStickers>) {
let buffer = Buffer()

View File

@ -101,7 +101,7 @@ public extension Api {
}
}
public extension Api {
enum InputMedia: TypeConstructorDescription {
indirect enum InputMedia: TypeConstructorDescription {
case inputMediaContact(phoneNumber: String, firstName: String, lastName: String, vcard: String)
case inputMediaDice(emoticon: String)
case inputMediaDocument(flags: Int32, id: Api.InputDocument, ttlSeconds: Int32?, query: String?)
@ -110,7 +110,7 @@ public extension Api {
case inputMediaGame(id: Api.InputGame)
case inputMediaGeoLive(flags: Int32, geoPoint: Api.InputGeoPoint, heading: Int32?, period: Int32?, proximityNotificationRadius: Int32?)
case inputMediaGeoPoint(geoPoint: Api.InputGeoPoint)
case inputMediaInvoice(flags: Int32, title: String, description: String, photo: Api.InputWebDocument?, invoice: Api.Invoice, payload: Buffer, provider: String, providerData: Api.DataJSON, startParam: String?)
case inputMediaInvoice(flags: Int32, title: String, description: String, photo: Api.InputWebDocument?, invoice: Api.Invoice, payload: Buffer, provider: String, providerData: Api.DataJSON, startParam: String?, extendedMedia: Api.InputMedia?)
case inputMediaPhoto(flags: Int32, id: Api.InputPhoto, ttlSeconds: Int32?)
case inputMediaPhotoExternal(flags: Int32, url: String, ttlSeconds: Int32?)
case inputMediaPoll(flags: Int32, poll: Api.Poll, correctAnswers: [Buffer]?, solution: String?, solutionEntities: [Api.MessageEntity]?)
@ -180,9 +180,9 @@ public extension Api {
}
geoPoint.serialize(buffer, true)
break
case .inputMediaInvoice(let flags, let title, let description, let photo, let invoice, let payload, let provider, let providerData, let startParam):
case .inputMediaInvoice(let flags, let title, let description, let photo, let invoice, let payload, let provider, let providerData, let startParam, let extendedMedia):
if boxed {
buffer.appendInt32(-646342540)
buffer.appendInt32(-1900697899)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(title, buffer: buffer, boxed: false)
@ -193,6 +193,7 @@ public extension Api {
serializeString(provider, buffer: buffer, boxed: false)
providerData.serialize(buffer, true)
if Int(flags) & Int(1 << 1) != 0 {serializeString(startParam!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 2) != 0 {extendedMedia!.serialize(buffer, true)}
break
case .inputMediaPhoto(let flags, let id, let ttlSeconds):
if boxed {
@ -293,8 +294,8 @@ public extension Api {
return ("inputMediaGeoLive", [("flags", String(describing: flags)), ("geoPoint", String(describing: geoPoint)), ("heading", String(describing: heading)), ("period", String(describing: period)), ("proximityNotificationRadius", String(describing: proximityNotificationRadius))])
case .inputMediaGeoPoint(let geoPoint):
return ("inputMediaGeoPoint", [("geoPoint", String(describing: geoPoint))])
case .inputMediaInvoice(let flags, let title, let description, let photo, let invoice, let payload, let provider, let providerData, let startParam):
return ("inputMediaInvoice", [("flags", String(describing: flags)), ("title", String(describing: title)), ("description", String(describing: description)), ("photo", String(describing: photo)), ("invoice", String(describing: invoice)), ("payload", String(describing: payload)), ("provider", String(describing: provider)), ("providerData", String(describing: providerData)), ("startParam", String(describing: startParam))])
case .inputMediaInvoice(let flags, let title, let description, let photo, let invoice, let payload, let provider, let providerData, let startParam, let extendedMedia):
return ("inputMediaInvoice", [("flags", String(describing: flags)), ("title", String(describing: title)), ("description", String(describing: description)), ("photo", String(describing: photo)), ("invoice", String(describing: invoice)), ("payload", String(describing: payload)), ("provider", String(describing: provider)), ("providerData", String(describing: providerData)), ("startParam", String(describing: startParam)), ("extendedMedia", String(describing: extendedMedia))])
case .inputMediaPhoto(let flags, let id, let ttlSeconds):
return ("inputMediaPhoto", [("flags", String(describing: flags)), ("id", String(describing: id)), ("ttlSeconds", String(describing: ttlSeconds))])
case .inputMediaPhotoExternal(let flags, let url, let ttlSeconds):
@ -459,6 +460,10 @@ public extension Api {
}
var _9: String?
if Int(_1!) & Int(1 << 1) != 0 {_9 = parseString(reader) }
var _10: Api.InputMedia?
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
_10 = Api.parse(reader, signature: signature) as? Api.InputMedia
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
@ -468,8 +473,9 @@ public extension Api {
let _c7 = _7 != nil
let _c8 = _8 != nil
let _c9 = (Int(_1!) & Int(1 << 1) == 0) || _9 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 {
return Api.InputMedia.inputMediaInvoice(flags: _1!, title: _2!, description: _3!, photo: _4, invoice: _5!, payload: _6!, provider: _7!, providerData: _8!, startParam: _9)
let _c10 = (Int(_1!) & Int(1 << 2) == 0) || _10 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 {
return Api.InputMedia.inputMediaInvoice(flags: _1!, title: _2!, description: _3!, photo: _4, invoice: _5!, payload: _6!, provider: _7!, providerData: _8!, startParam: _9, extendedMedia: _10)
}
else {
return nil

View File

@ -677,7 +677,6 @@ public extension Api {
case inputStickerSetAnimatedEmoji
case inputStickerSetAnimatedEmojiAnimations
case inputStickerSetDice(emoticon: String)
case inputStickerSetEmojiDefaultStatuses
case inputStickerSetEmojiGenericAnimations
case inputStickerSetEmpty
case inputStickerSetID(id: Int64, accessHash: Int64)
@ -703,12 +702,6 @@ public extension Api {
buffer.appendInt32(-427863538)
}
serializeString(emoticon, buffer: buffer, boxed: false)
break
case .inputStickerSetEmojiDefaultStatuses:
if boxed {
buffer.appendInt32(701560302)
}
break
case .inputStickerSetEmojiGenericAnimations:
if boxed {
@ -752,8 +745,6 @@ public extension Api {
return ("inputStickerSetAnimatedEmojiAnimations", [])
case .inputStickerSetDice(let emoticon):
return ("inputStickerSetDice", [("emoticon", String(describing: emoticon))])
case .inputStickerSetEmojiDefaultStatuses:
return ("inputStickerSetEmojiDefaultStatuses", [])
case .inputStickerSetEmojiGenericAnimations:
return ("inputStickerSetEmojiGenericAnimations", [])
case .inputStickerSetEmpty:
@ -784,9 +775,6 @@ public extension Api {
return nil
}
}
public static func parse_inputStickerSetEmojiDefaultStatuses(_ reader: BufferReader) -> InputStickerSet? {
return Api.InputStickerSet.inputStickerSetEmojiDefaultStatuses
}
public static func parse_inputStickerSetEmojiGenericAnimations(_ reader: BufferReader) -> InputStickerSet? {
return Api.InputStickerSet.inputStickerSetEmojiGenericAnimations
}

View File

@ -115,6 +115,7 @@ enum AccountStateMutationOperation {
case UpdateAttachMenuBots
case UpdateAudioTranscription(messageId: MessageId, id: Int64, isPending: Bool, text: String)
case UpdateConfig
case UpdateExtendedMedia(MessageId, Api.MessageExtendedMedia)
}
struct HoleFromPreviousState {
@ -271,7 +272,7 @@ struct AccountMutableState {
mutating func updateMessageReactions(_ messageId: MessageId, reactions: Api.MessageReactions, eventTimestamp: Int32?) {
self.addOperation(.UpdateMessageReactions(messageId, reactions, eventTimestamp))
}
mutating func updateMedia(_ id: MediaId, media: Media?) {
self.addOperation(.UpdateMedia(id, media))
}
@ -522,9 +523,13 @@ struct AccountMutableState {
self.addOperation(.UpdateConfig)
}
mutating func updateExtendedMedia(_ messageId: MessageId, extendedMedia: Api.MessageExtendedMedia) {
self.addOperation(.UpdateExtendedMedia(messageId, extendedMedia))
}
mutating func addOperation(_ operation: AccountStateMutationOperation) {
switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig:
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia:
break
case let .AddMessages(messages, location):
for message in messages {

View File

@ -189,6 +189,7 @@ private var declaredEncodables: Void = {
declareEncodable(SendAsMessageAttribute.self, f: { SendAsMessageAttribute(decoder: $0) })
declareEncodable(AudioTranscriptionMessageAttribute.self, f: { AudioTranscriptionMessageAttribute(decoder: $0) })
declareEncodable(NonPremiumMessageAttribute.self, f: { NonPremiumMessageAttribute(decoder: $0) })
declareEncodable(TelegramExtendedMedia.self, f: { TelegramExtendedMedia(decoder: $0) })
return
}()

View File

@ -478,7 +478,7 @@ extension ChatContextResultMessage {
if let replyMarkup = replyMarkup {
parsedReplyMarkup = ReplyMarkupMessageAttribute(apiMarkup: replyMarkup)
}
self = .invoice(media: TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: nil, currency: currency, totalAmount: totalAmount, startParam: "", flags: parsedFlags), replyMarkup: parsedReplyMarkup)
self = .invoice(media: TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: nil, currency: currency, totalAmount: totalAmount, startParam: "", extendedMedia: nil, flags: parsedFlags), replyMarkup: parsedReplyMarkup)
}
}
}

View File

@ -256,7 +256,7 @@ func apiMessageAssociatedMessageIds(_ message: Api.Message) -> [MessageId]? {
return nil
}
func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerId:PeerId) -> (Media?, Int32?, Bool?) {
func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerId: PeerId) -> (Media?, Int32?, Bool?) {
if let media = media {
switch media {
case let .messageMediaPhoto(_, photo, ttlSeconds):
@ -298,7 +298,7 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
break
case let .messageMediaGame(game):
return (TelegramMediaGame(apiGame: game), nil, nil)
case let .messageMediaInvoice(flags, title, description, photo, receiptMsgId, currency, totalAmount, startParam):
case let .messageMediaInvoice(flags, title, description, photo, receiptMsgId, currency, totalAmount, startParam, apiExtendedMedia):
var parsedFlags = TelegramMediaInvoiceFlags()
if (flags & (1 << 3)) != 0 {
parsedFlags.insert(.isTest)
@ -306,7 +306,33 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
if (flags & (1 << 1)) != 0 {
parsedFlags.insert(.shippingAddressRequested)
}
return (TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: receiptMsgId.flatMap { MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) }, currency: currency, totalAmount: totalAmount, startParam: startParam, flags: parsedFlags), nil, nil)
let extendedMedia: TelegramExtendedMedia?
if let apiExtendedMedia = apiExtendedMedia {
switch apiExtendedMedia {
case let .messageExtendedMediaPreview(_, width, height, thumb, videoDuration):
var dimensions: PixelDimensions?
if let width = width, let height = height {
dimensions = PixelDimensions(width: width, height: height)
}
var immediateThumbnailData: Data?
if let thumb = thumb, case let .photoStrippedSize(_, bytes) = thumb {
immediateThumbnailData = bytes.makeData()
}
extendedMedia = .preview(dimensions: dimensions, immediateThumbnailData: immediateThumbnailData, videoDuration: videoDuration)
case let .messageExtendedMedia(apiMedia):
let (media, _, _) = textMediaAndExpirationTimerFromApiMedia(apiMedia, peerId)
if let media = media {
extendedMedia = .full(media: media)
} else {
extendedMedia = nil
}
}
} else {
extendedMedia = nil
}
return (TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: receiptMsgId.flatMap { MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) }, currency: currency, totalAmount: totalAmount, startParam: startParam, extendedMedia: extendedMedia, flags: parsedFlags), nil, nil)
case let .messageMediaPoll(poll, results):
switch poll {
case let .poll(id, flags, question, answers, closePeriod, _):

View File

@ -66,8 +66,6 @@ extension StickerPackReference {
self = .premiumGifts
case .inputStickerSetEmojiGenericAnimations:
self = .emojiGenericAnimations
case .inputStickerSetEmojiDefaultStatuses:
return nil
}
}
}

View File

@ -1545,6 +1545,8 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
updatedState.addDismissWebView(queryId)
case .updateConfig:
updatedState.reloadConfig()
case let .updateMessageExtendedMedia(peer, msgId, extendedMedia):
updatedState.updateExtendedMedia(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: msgId), extendedMedia: extendedMedia)
default:
break
}
@ -2370,7 +2372,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
var currentAddScheduledMessages: OptimizeAddMessagesState?
for operation in operations {
switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig:
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia:
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
}
@ -3430,6 +3432,50 @@ func replayFinalState(
})
case .UpdateConfig:
updateConfig = true
case let .UpdateExtendedMedia(messageId, apiExtendedMedia):
transaction.updateMessage(messageId, update: { currentMessage in
var media = currentMessage.media
let invoice = media.first(where: { $0 is TelegramMediaInvoice }) as? TelegramMediaInvoice
let currentExtendedMedia = invoice?.extendedMedia
var storeForwardInfo: StoreMessageForwardInfo?
if let forwardInfo = currentMessage.forwardInfo {
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags)
}
let updatedExtendedMedia: TelegramExtendedMedia?
switch apiExtendedMedia {
case let .messageExtendedMediaPreview(_, width, height, thumb, videoDuration):
var dimensions: PixelDimensions?
if let width = width, let height = height {
dimensions = PixelDimensions(width: width, height: height)
}
var immediateThumbnailData: Data?
if let thumb = thumb, case let .photoStrippedSize(_, bytes) = thumb {
immediateThumbnailData = bytes.makeData()
}
updatedExtendedMedia = .preview(dimensions: dimensions, immediateThumbnailData: immediateThumbnailData, videoDuration: videoDuration)
case let .messageExtendedMedia(apiMedia):
let (media, _, _) = textMediaAndExpirationTimerFromApiMedia(apiMedia, currentMessage.id.peerId)
if let media = media {
updatedExtendedMedia = .full(media: media)
} else {
updatedExtendedMedia = currentExtendedMedia
}
}
if let updatedExtendedMedia = updatedExtendedMedia, var invoice = invoice {
if let currentExtendedMedia = currentExtendedMedia, case .full = currentExtendedMedia, case .preview = updatedExtendedMedia {
} else {
media = media.filter { !($0 is TelegramMediaInvoice) }
invoice = invoice.withUpdatedExtendedMedia(updatedExtendedMedia)
media.append(invoice)
}
}
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: media))
})
}
}

View File

@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
public class Serialization: NSObject, MTSerialization {
public func currentLayer() -> UInt {
return 145
return 146
}
public func parseMessage(_ data: Data!) -> Any! {

View File

@ -16,6 +16,77 @@ public struct TelegramMediaInvoiceFlags: OptionSet {
public static let shippingAddressRequested = TelegramMediaInvoiceFlags(rawValue: 1 << 1)
}
public enum TelegramExtendedMedia: PostboxCoding, Equatable {
public static func == (lhs: TelegramExtendedMedia, rhs: TelegramExtendedMedia) -> Bool {
switch lhs {
case let .preview(lhsDimensions, lhsImmediateThumbnailData, lhsVideoDuration):
if case let .preview(rhsDimensions, rhsImmediateThumbnailData, rhsVideoDuration) = rhs, lhsDimensions == rhsDimensions, lhsImmediateThumbnailData == rhsImmediateThumbnailData, lhsVideoDuration == rhsVideoDuration {
return true
} else {
return false
}
case let .full(lhsMedia):
if case let .full(rhsMedia) = rhs, lhsMedia.isEqual(to: rhsMedia) {
return true
} else {
return false
}
}
}
case preview(dimensions: PixelDimensions?, immediateThumbnailData: Data?, videoDuration: Int32?)
case full(media: Media)
public init(decoder: PostboxDecoder) {
let type = decoder.decodeInt32ForKey("t", orElse: 0)
switch type {
case 0:
let width = decoder.decodeOptionalInt32ForKey("width")
let height = decoder.decodeOptionalInt32ForKey("width")
var dimensions: PixelDimensions?
if let width = width, let height = height {
dimensions = PixelDimensions(width: width, height: height)
}
let immediateThumbnailData = decoder.decodeDataForKey("thumb")
let videoDuration = decoder.decodeOptionalInt32ForKey("duration")
self = .preview(dimensions: dimensions, immediateThumbnailData: immediateThumbnailData, videoDuration: videoDuration)
case 1:
let media = decoder.decodeObjectForKey("media") as! Media
self = .full(media: media)
default:
self = .preview(dimensions: nil, immediateThumbnailData: nil, videoDuration: nil)
fatalError()
}
}
public func encode(_ encoder: PostboxEncoder) {
switch self {
case let .preview(dimensions, immediateThumbnailData, videoDuration):
encoder.encodeInt32(0, forKey: "t")
if let dimensions = dimensions {
encoder.encodeInt32(dimensions.width, forKey: "width")
encoder.encodeInt32(dimensions.height, forKey: "height")
} else {
encoder.encodeNil(forKey: "width")
encoder.encodeNil(forKey: "height")
}
if let immediateThumbnailData = immediateThumbnailData {
encoder.encodeData(immediateThumbnailData, forKey: "thumb")
} else {
encoder.encodeNil(forKey: "thumb")
}
if let videoDuration = videoDuration {
encoder.encodeInt32(videoDuration, forKey: "duration")
} else {
encoder.encodeNil(forKey: "duration")
}
case let .full(media):
encoder.encodeInt32(1, forKey: "t")
encoder.encodeObject(media, forKey: "media")
}
}
}
public final class TelegramMediaInvoice: Media {
public var peerIds: [PeerId] = []
@ -29,8 +100,9 @@ public final class TelegramMediaInvoice: Media {
public let startParam: String
public let photo: TelegramMediaWebFile?
public let flags: TelegramMediaInvoiceFlags
public let extendedMedia: TelegramExtendedMedia?
public init(title: String, description: String, photo: TelegramMediaWebFile?, receiptMessageId: MessageId?, currency: String, totalAmount: Int64, startParam: String, flags: TelegramMediaInvoiceFlags) {
public init(title: String, description: String, photo: TelegramMediaWebFile?, receiptMessageId: MessageId?, currency: String, totalAmount: Int64, startParam: String, extendedMedia: TelegramExtendedMedia?, flags: TelegramMediaInvoiceFlags) {
self.title = title
self.description = description
self.photo = photo
@ -39,6 +111,7 @@ public final class TelegramMediaInvoice: Media {
self.totalAmount = totalAmount
self.startParam = startParam
self.flags = flags
self.extendedMedia = extendedMedia
}
public init(decoder: PostboxDecoder) {
@ -49,6 +122,7 @@ public final class TelegramMediaInvoice: Media {
self.startParam = decoder.decodeStringForKey("sp", orElse: "")
self.photo = decoder.decodeObjectForKey("p") as? TelegramMediaWebFile
self.flags = TelegramMediaInvoiceFlags(rawValue: decoder.decodeInt32ForKey("f", orElse: 0))
self.extendedMedia = decoder.decodeObjectForKey("m", decoder: { TelegramExtendedMedia(decoder: $0) }) as? TelegramExtendedMedia
if let receiptMessageIdPeerId = decoder.decodeOptionalInt64ForKey("r.p") as Int64?, let receiptMessageIdNamespace = decoder.decodeOptionalInt32ForKey("r.n") as Int32?, let receiptMessageIdId = decoder.decodeOptionalInt32ForKey("r.i") as Int32? {
self.receiptMessageId = MessageId(peerId: PeerId(receiptMessageIdPeerId), namespace: receiptMessageIdNamespace, id: receiptMessageIdId)
@ -65,12 +139,18 @@ public final class TelegramMediaInvoice: Media {
encoder.encodeString(self.startParam, forKey: "sp")
encoder.encodeInt32(self.flags.rawValue, forKey: "f")
if let photo = photo {
if let photo = self.photo {
encoder.encodeObject(photo, forKey: "p")
} else {
encoder.encodeNil(forKey: "p")
}
if let extendedMedia = self.extendedMedia {
encoder.encodeObject(extendedMedia, forKey: "m")
} else {
encoder.encodeNil(forKey: "m")
}
if let receiptMessageId = self.receiptMessageId {
encoder.encodeInt64(receiptMessageId.peerId.toInt64(), forKey: "r.p")
encoder.encodeInt32(receiptMessageId.namespace, forKey: "r.n")
@ -121,4 +201,17 @@ public final class TelegramMediaInvoice: Media {
public func isSemanticallyEqual(to other: Media) -> Bool {
return self.isEqual(to: other)
}
public func withUpdatedExtendedMedia(_ extendedMedia: TelegramExtendedMedia) -> TelegramMediaInvoice {
return TelegramMediaInvoice(
title: self.title,
description: self.description,
photo: self.photo,
receiptMessageId: self.receiptMessageId,
currency: self.currency,
totalAmount: self.totalAmount,
startParam: self.startParam,
extendedMedia: extendedMedia,
flags: self.flags)
}
}

View File

@ -239,7 +239,7 @@ func _internal_fetchBotPaymentInvoice(postbox: Postbox, network: Network, source
parsedFlags.insert(.shippingAddressRequested)
}
return TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: nil, currency: parsedInvoice.currency, totalAmount: 0, startParam: "", flags: parsedFlags)
return TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: nil, currency: parsedInvoice.currency, totalAmount: 0, startParam: "", extendedMedia: nil, flags: parsedFlags)
}
}
|> mapError { _ -> BotPaymentFormRequestError in }
@ -612,6 +612,7 @@ func _internal_requestBotPaymentReceipt(account: Account, messageId: MessageId)
currency: currency,
totalAmount: totalAmount,
startParam: "",
extendedMedia: nil,
flags: []
)

View File

@ -636,6 +636,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var openMessageByAction = false
var isLocation = false
for media in message.media {
if media is TelegramMediaMap {
isLocation = true
@ -646,7 +647,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.chatDisplayNode.dismissInput()
}
}
if let action = media as? TelegramMediaAction {
if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia {
switch extendedMedia {
case .preview:
strongSelf.controllerInteraction?.openCheckoutOrReceipt(message.id)
return true
case .full:
break
}
} else if let action = media as? TelegramMediaAction {
switch action.action {
case .pinnedMessageUpdated:
for attribute in message.attributes {

View File

@ -114,9 +114,13 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
result.append((message, ChatMessageGameBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
needReactions = false
break inner
} else if let _ = media as? TelegramMediaInvoice {
skipText = true
result.append((message, ChatMessageInvoiceBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
} else if let invoice = media as? TelegramMediaInvoice {
if let _ = invoice.extendedMedia {
result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default)))
} else {
skipText = true
result.append((message, ChatMessageInvoiceBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
}
needReactions = false
break inner
} else if let _ = media as? TelegramMediaContact {
@ -1376,7 +1380,33 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
replyMessage = firstMessage.associatedMessages[attribute.messageId]
}
} else if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), !attribute.rows.isEmpty && !isPreview {
replyMarkup = attribute
var isExtendedMedia = false
for media in firstMessage.media {
if let invoice = media as? TelegramMediaInvoice, let _ = invoice.extendedMedia {
isExtendedMedia = true
break
}
}
if isExtendedMedia {
var updatedRows: [ReplyMarkupRow] = []
for row in attribute.rows {
let updatedButtons = row.buttons.filter { button in
if case .payment = button.action {
return false
} else {
return true
}
}
if !updatedButtons.isEmpty {
updatedRows.append(ReplyMarkupRow(buttons: updatedButtons))
}
}
if !updatedRows.isEmpty {
replyMarkup = ReplyMarkupMessageAttribute(rows: updatedRows, flags: attribute.flags, placeholder: attribute.placeholder)
}
} else {
replyMarkup = attribute
}
} else if let attribute = attribute as? AuthorSignatureMessageAttribute {
if let chatPeer = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case .group = chatPeer.info, firstMessage.author is TelegramChannel, !attribute.signature.isEmpty {
authorRank = .custom(attribute.signature)

View File

@ -75,6 +75,89 @@ struct ChatMessageDateAndStatus {
var dateText: String
}
private class ExtendedMediaOverlayNode: ASDisplayNode {
private let buttonNode: HighlightTrackingButtonNode
private let blurNode: NavigationBackgroundNode
private let highlightedBackgroundNode: ASDisplayNode
private let iconNode: ASImageNode
private let textNode: ImmediateTextNode
override init() {
self.buttonNode = HighlightTrackingButtonNode()
self.buttonNode.clipsToBounds = true
self.buttonNode.cornerRadius = 16.0
self.blurNode = NavigationBackgroundNode(color: .clear)
self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.3)
self.highlightedBackgroundNode.alpha = 0.0
self.iconNode = ASImageNode()
self.iconNode.displaysAsynchronously = false
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/SmallLock"), color: .white)
self.textNode = ImmediateTextNode()
super.init()
self.clipsToBounds = true
self.cornerRadius = 16.0
self.isUserInteractionEnabled = false
self.addSubnode(self.buttonNode)
self.buttonNode.addSubnode(self.blurNode)
self.buttonNode.addSubnode(self.highlightedBackgroundNode)
self.addSubnode(self.iconNode)
self.addSubnode(self.textNode)
self.buttonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.highlightedBackgroundNode.layer.removeAnimation(forKey: "opacity")
strongSelf.highlightedBackgroundNode.alpha = 1.0
} else {
strongSelf.highlightedBackgroundNode.alpha = 0.0
strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
}
}
}
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
}
@objc private func buttonPressed() {
}
override func didLoad() {
super.didLoad()
if #available(iOS 13.0, *) {
self.buttonNode.layer.cornerCurve = .continuous
}
}
func update(size: CGSize, text: String) {
let spacing: CGFloat = 2.0
let padding: CGFloat = 10.0
self.textNode.attributedText = NSAttributedString(string: text, font: Font.semibold(14.0), textColor: .white, paragraphAlignment: .center)
let textSize = self.textNode.updateLayout(size)
if let iconSize = self.iconNode.image?.size {
let contentSize = CGSize(width: iconSize.width + textSize.width + spacing + padding * 2.0, height: 32.0)
self.buttonNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - contentSize.width) / 2.0), y: floorToScreenPixels((size.height - contentSize.height) / 2.0)), size: contentSize)
self.highlightedBackgroundNode.frame = CGRect(origin: .zero, size: contentSize)
self.blurNode.frame = self.highlightedBackgroundNode.frame
self.blurNode.update(size: self.blurNode.frame.size, transition: .immediate)
self.blurNode.updateColor(color: UIColor(rgb: 0xffffff, alpha: 0.2), enableBlur: true, transition: .immediate)
self.iconNode.frame = CGRect(origin: CGPoint(x: self.buttonNode.frame.minX + padding, y: self.buttonNode.frame.minY + floorToScreenPixels((contentSize.height - iconSize.height) / 2.0) + 1.0), size: iconSize)
self.textNode.frame = CGRect(origin: CGPoint(x: self.iconNode.frame.maxX + spacing, y: self.buttonNode.frame.minY + floorToScreenPixels((contentSize.height - textSize.height) / 2.0)), size: textSize)
}
}
}
final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitionNode {
private let pinchContainerNode: PinchSourceContainerNode
private let imageNode: TransformImageNode
@ -92,6 +175,9 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
}
let dateAndStatusNode: ChatMessageDateAndStatusNode
private var badgeNode: ChatMessageInteractiveMediaBadge?
private var extendedMediaOverlayNode: ExtendedMediaOverlayNode?
//private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer?
private var context: AccountContext?
@ -152,7 +238,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
var activateLocalContent: (InteractiveMediaNodeActivateContent) -> Void = { _ in }
var activatePinch: ((PinchSourceContainerNode) -> Void)?
var updateMessageReaction: ((Message, ChatControllerInteractionReaction) -> Void)?
override init() {
self.pinchContainerNode = PinchSourceContainerNode()
@ -167,7 +253,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
self.imageNode.displaysAsynchronously = false
self.pinchContainerNode.contentNode.addSubnode(self.imageNode)
self.pinchContainerNode.activate = { [weak self] sourceNode in
guard let strongSelf = self else {
return
@ -308,11 +394,17 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
case .Fetching:
if let context = self.context, let message = self.message, message.flags.isSending {
let _ = context.engine.messages.deleteMessagesInteractively(messageIds: [message.id], type: .forEveryone).start()
} else if let media = media, let context = self.context, let message = message {
} else if let media = self.media, let context = self.context, let message = self.message {
if let media = media as? TelegramMediaFile {
messageMediaFileCancelInteractiveFetch(context: context, messageId: message.id, file: media)
} else if let media = media as? TelegramMediaImage, let resource = largestImageRepresentation(media.representations)?.resource {
messageMediaImageCancelInteractiveFetch(context: context, messageId: message.id, image: media, resource: resource)
} else if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia, case let .full(media) = extendedMedia {
if let media = media as? TelegramMediaFile {
messageMediaFileCancelInteractiveFetch(context: context, messageId: message.id, file: media)
} else if let media = media as? TelegramMediaImage, let resource = largestImageRepresentation(media.representations)?.resource {
messageMediaImageCancelInteractiveFetch(context: context, messageId: message.id, image: media, resource: resource)
}
}
}
if let cancel = self.fetchControls.with({ return $0?.cancel }) {
@ -347,7 +439,11 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
self.progressPressed(canActivate: true)
}
} else {
self.progressPressed(canActivate: true)
if let invoice = self.media as? TelegramMediaInvoice, let _ = invoice.extendedMedia {
self.activateLocalContent(.default)
} else {
self.progressPressed(canActivate: true)
}
}
}
}
@ -420,6 +516,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
}
}
var isExtendedMediaPreview = false
var isInlinePlayableVideo = false
var isSticker = false
var maxDimensions = layoutConstants.image.maxDimensions
@ -482,6 +579,42 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
case .color, .gradient:
unboundSize = CGSize(width: 128.0, height: 128.0)
}
} else if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia {
switch extendedMedia {
case let .preview(dimensions, _, _):
if let dimensions = dimensions {
unboundSize = CGSize(width: max(10.0, floor(dimensions.cgSize.width * 0.5)), height: max(10.0, floor(dimensions.cgSize.height * 0.5)))
} else {
unboundSize = CGSize(width: 200.0, height: 100.0)
}
isExtendedMediaPreview = true
case let .full(media):
if let image = media as? TelegramMediaImage, let dimensions = largestImageRepresentation(image.representations)?.dimensions {
unboundSize = CGSize(width: max(10.0, floor(dimensions.cgSize.width * 0.5)), height: max(10.0, floor(dimensions.cgSize.height * 0.5)))
} else if let file = media as? TelegramMediaFile, var dimensions = file.dimensions {
if let thumbnail = file.previewRepresentations.first {
let dimensionsVertical = dimensions.width < dimensions.height
let thumbnailVertical = thumbnail.dimensions.width < thumbnail.dimensions.height
if dimensionsVertical != thumbnailVertical {
dimensions = PixelDimensions(CGSize(width: dimensions.cgSize.height, height: dimensions.cgSize.width))
}
}
unboundSize = CGSize(width: floor(dimensions.cgSize.width * 0.5), height: floor(dimensions.cgSize.height * 0.5))
if file.isAnimated {
unboundSize = unboundSize.aspectFilled(CGSize(width: 480.0, height: 480.0))
} else if file.isVideo && !file.isAnimated, case let .constrained(constrainedSize) = sizeCalculation {
if unboundSize.width > unboundSize.height {
maxDimensions = CGSize(width: constrainedSize.width, height: layoutConstants.video.maxHorizontalHeight)
} else {
maxDimensions = CGSize(width: constrainedSize.width, height: layoutConstants.video.maxVerticalHeight)
}
maxHeight = maxDimensions.height
}
isInlinePlayableVideo = file.isVideo && !isSecretMedia
} else {
unboundSize = CGSize(width: 54.0, height: 54.0)
}
}
} else {
unboundSize = CGSize(width: 54.0, height: 54.0)
}
@ -653,6 +786,17 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
}
if mediaUpdated || isSendingUpdated || automaticPlaybackUpdated {
var media = media
if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia {
switch extendedMedia {
case let .preview(_, immediateThumbnailData, _):
let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: [])
media = thumbnailMedia
case let .full(fullMedia):
media = fullMedia
}
}
if let image = media as? TelegramMediaImage {
if hasCurrentVideoNode {
replaceVideoNode = true
@ -830,6 +974,11 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
}
if statusUpdated {
var media = media
if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia, case let .full(fullMedia) = extendedMedia {
media = fullMedia
}
if let image = media as? TelegramMediaImage {
if message.flags.isSending {
updatedStatusSignal = combineLatest(chatMessagePhotoStatus(context: context, messageId: message.id, photoReference: .message(message: MessageReference(message), media: image)), context.account.pendingMessageManager.pendingMessageStatus(message.id) |> map { $0.0 })
@ -869,9 +1018,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
}
}
}
let arguments = TransformImageArguments(corners: corners, imageSize: drawingSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), resizeMode: isInlinePlayableVideo ? .fill(.black) : .blurBackground, emptyColor: emptyColor, custom: patternArguments)
let imageFrame = CGRect(origin: CGPoint(x: -arguments.insets.left, y: -arguments.insets.top), size: arguments.drawingSize).ensuredValid
@ -1089,6 +1236,12 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
if let updatedFetchControls = updatedFetchControls {
let _ = strongSelf.fetchControls.swap(updatedFetchControls)
var media = media
if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia, case let .full(fullMedia) = extendedMedia {
media = fullMedia
}
if case .full = automaticDownload {
if let _ = media as? TelegramMediaImage {
updatedFetchControls.fetch(false)
@ -1131,7 +1284,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
strongSelf.updateStatus(animated: synchronousLoads)
strongSelf.pinchContainerNode.isPinchGestureEnabled = !isSecretMedia
strongSelf.pinchContainerNode.isPinchGestureEnabled = !isSecretMedia && !isExtendedMediaPreview
}
})
})
@ -1250,23 +1403,29 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
var mediaDownloadState: ChatMessageInteractiveMediaDownloadState?
let messageTheme = theme.chat.message
if let invoice = invoice {
let string = NSMutableAttributedString()
if invoice.receiptMessageId != nil {
var title = strings.Checkout_Receipt_Title.uppercased()
if invoice.flags.contains(.isTest) {
title += " (Test)"
if let extendedMedia = invoice.extendedMedia {
if case let .preview(_, _, maybeVideoDuration) = extendedMedia, let videoDuration = maybeVideoDuration {
badgeContent = .text(inset: 0.0, backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, text: NSAttributedString(string: stringForDuration(videoDuration, position: nil)))
}
string.append(NSAttributedString(string: title))
} else {
string.append(NSAttributedString(string: "\(formatCurrencyAmount(invoice.totalAmount, currency: invoice.currency)) ", attributes: [ChatTextInputAttributes.bold: true as NSNumber]))
var title = strings.Message_InvoiceLabel
if invoice.flags.contains(.isTest) {
title += " (Test)"
let string = NSMutableAttributedString()
if invoice.receiptMessageId != nil {
var title = strings.Checkout_Receipt_Title.uppercased()
if invoice.flags.contains(.isTest) {
title += " (Test)"
}
string.append(NSAttributedString(string: title))
} else {
string.append(NSAttributedString(string: "\(formatCurrencyAmount(invoice.totalAmount, currency: invoice.currency)) ", attributes: [ChatTextInputAttributes.bold: true as NSNumber]))
var title = strings.Message_InvoiceLabel
if invoice.flags.contains(.isTest) {
title += " (Test)"
}
string.append(NSAttributedString(string: title))
}
string.append(NSAttributedString(string: title))
badgeContent = .text(inset: 0.0, backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, text: string)
}
badgeContent = .text(inset: 0.0, backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, text: string)
}
var animated = animated
if let updatingMedia = attributes.updatingMedia, case .update = updatingMedia.media {
@ -1513,7 +1672,15 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
if let badgeContent = badgeContent {
if self.badgeNode == nil {
let badgeNode = ChatMessageInteractiveMediaBadge()
badgeNode.frame = CGRect(origin: CGPoint(x: 6.0, y: 6.0), size: CGSize(width: radialStatusSize, height: radialStatusSize))
let incoming: Bool
if let context = self.context, let message = self.message {
incoming = message.effectivelyIncoming(context.account.peerId)
} else {
incoming = false
}
badgeNode.frame = CGRect(origin: CGPoint(x: incoming ? 10.0 : 6.0, y: 6.0), size: CGSize(width: radialStatusSize, height: radialStatusSize))
badgeNode.pressed = { [weak self] in
guard let strongSelf = self, let fetchStatus = strongSelf.fetchStatus else {
return
@ -1536,6 +1703,36 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
badgeNode.removeFromSupernode()
}
if let invoice = invoice, let extendedMedia = invoice.extendedMedia, case .preview = extendedMedia {
if self.extendedMediaOverlayNode == nil {
let extendedMediaOverlayNode = ExtendedMediaOverlayNode()
self.extendedMediaOverlayNode = extendedMediaOverlayNode
self.pinchContainerNode.contentNode.insertSubnode(extendedMediaOverlayNode, aboveSubnode: self.imageNode)
}
self.extendedMediaOverlayNode?.frame = self.imageNode.frame
var paymentText: String = ""
outer: for attribute in message.attributes {
if let attribute = attribute as? ReplyMarkupMessageAttribute {
for row in attribute.rows {
for button in row.buttons {
if case .payment = button.action {
paymentText = button.title
break outer
}
}
}
break
}
}
self.extendedMediaOverlayNode?.update(size: self.imageNode.frame.size, text: paymentText)
} else if let extendedMediaOverlayNode = self.extendedMediaOverlayNode {
self.extendedMediaOverlayNode = nil
extendedMediaOverlayNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak extendedMediaOverlayNode] _ in
extendedMediaOverlayNode?.removeFromSupernode()
})
}
if isSecretMedia, secretBeginTimeAndTimeout?.0 != nil {
if self.secretTimer == nil {
self.secretTimer = SwiftSignalKit.Timer(timeout: 0.3, repeat: true, completion: { [weak self] in

View File

@ -114,6 +114,39 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
}
}
contentMode = .aspectFill
} else if let invoice = media as? TelegramMediaInvoice {
selectedMedia = invoice
if let extendedMedia = invoice.extendedMedia, case let .full(media) = extendedMedia {
if let telegramImage = media as? TelegramMediaImage {
if shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, authorPeerId: item.message.author?.id, contactsPeerIds: item.associatedData.contactsPeerIds, media: telegramImage) {
automaticDownload = .full
}
} else if let telegramFile = media as? TelegramMediaFile {
if shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, authorPeerId: item.message.author?.id, contactsPeerIds: item.associatedData.contactsPeerIds, media: telegramFile) {
automaticDownload = .full
} else if shouldPredownloadMedia(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, media: telegramFile) {
automaticDownload = .prefetch
}
if !item.message.containsSecretMedia {
if telegramFile.isAnimated && item.controllerInteraction.automaticMediaDownloadSettings.autoplayGifs {
if case .full = automaticDownload {
automaticPlayback = true
} else {
automaticPlayback = item.context.account.postbox.mediaBox.completedResourcePath(telegramFile.resource) != nil
}
} else if (telegramFile.isVideo && !telegramFile.isAnimated) && item.controllerInteraction.automaticMediaDownloadSettings.autoplayVideos {
if case .full = automaticDownload {
automaticPlayback = true
} else {
automaticPlayback = item.context.account.postbox.mediaBox.completedResourcePath(telegramFile.resource) != nil
}
}
}
contentMode = .aspectFill
}
}
}
}
}
@ -121,7 +154,33 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
var hasReplyMarkup: Bool = false
for attribute in item.message.attributes {
if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), !attribute.rows.isEmpty {
hasReplyMarkup = true
var isExtendedMedia = false
for media in item.message.media {
if let invoice = media as? TelegramMediaInvoice, let _ = invoice.extendedMedia {
isExtendedMedia = true
break
}
}
if isExtendedMedia {
var updatedRows: [ReplyMarkupRow] = []
for row in attribute.rows {
let updatedButtons = row.buttons.filter { button in
if case .payment = button.action {
return false
} else {
return true
}
}
if !updatedButtons.isEmpty {
updatedRows.append(ReplyMarkupRow(buttons: updatedButtons))
}
}
if !updatedRows.isEmpty {
hasReplyMarkup = true
}
} else {
hasReplyMarkup = true
}
break
}
}