diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index d5f9f3b2d6..b7c5b36d85 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -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."; diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift index 585a67fb78..fa7c05a806 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift @@ -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 diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceEmailEntryControllerNode.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceEmailEntryControllerNode.swift index a88c675073..98920a6667 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceEmailEntryControllerNode.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceEmailEntryControllerNode.swift @@ -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 diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryControllerNode.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryControllerNode.swift index fe7a92f9b0..d45b45adeb 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryControllerNode.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryControllerNode.swift @@ -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 diff --git a/submodules/CodeInputView/Sources/CodeInputView.swift b/submodules/CodeInputView/Sources/CodeInputView.swift index f75b81f4ba..1113b52ca3 100644 --- a/submodules/CodeInputView/Sources/CodeInputView.swift +++ b/submodules/CodeInputView/Sources/CodeInputView.swift @@ -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 diff --git a/submodules/GalleryData/Sources/GalleryData.swift b/submodules/GalleryData/Sources/GalleryData.swift index 6308be5660..7de749538c 100644 --- a/submodules/GalleryData/Sources/GalleryData.swift +++ b/submodules/GalleryData/Sources/GalleryData.swift @@ -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 { diff --git a/submodules/GalleryUI/Sources/GalleryController.swift b/submodules/GalleryUI/Sources/GalleryController.swift index 26ed2e167b..f727cb38ac 100644 --- a/submodules/GalleryUI/Sources/GalleryController.swift +++ b/submodules/GalleryUI/Sources/GalleryController.swift @@ -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/") { diff --git a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift index 63f0d18bd1..e78b5bc6fc 100644 --- a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift @@ -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/") { diff --git a/submodules/PhotoResources/Sources/PhotoResources.swift b/submodules/PhotoResources/Sources/PhotoResources.swift index f058c95093..c26cc9c06d 100644 --- a/submodules/PhotoResources/Sources/PhotoResources.swift +++ b/submodules/PhotoResources/Sources/PhotoResources.swift @@ -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() } diff --git a/submodules/PremiumUI/Sources/EmojiHeaderComponent.swift b/submodules/PremiumUI/Sources/EmojiHeaderComponent.swift index 075667eceb..cb3ccfd066 100644 --- a/submodules/PremiumUI/Sources/EmojiHeaderComponent.swift +++ b/submodules/PremiumUI/Sources/EmojiHeaderComponent.swift @@ -111,14 +111,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 }) @@ -165,3 +169,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 +} diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift index e19d407c91..d8512abeb1 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift @@ -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)) diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift index 76db26f87d..84aee20ae0 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift @@ -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) { diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 01bfcc7fa4..dc74fdc40f 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -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,6 +354,7 @@ 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[80008398] = { return Api.InputStickerSet.parse_inputStickerSetEmojiGenericAnimations($0) } dict[-4838507] = { return Api.InputStickerSet.parse_inputStickerSetEmpty($0) } dict[-1645763991] = { return Api.InputStickerSet.parse_inputStickerSetID($0) } dict[-930399486] = { return Api.InputStickerSet.parse_inputStickerSetPremiumGifts($0) } @@ -465,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) } @@ -474,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) } @@ -797,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) } @@ -1408,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: diff --git a/submodules/TelegramApi/Sources/Api12.swift b/submodules/TelegramApi/Sources/Api12.swift index 7668430ca7..afdf054b37 100644 --- a/submodules/TelegramApi/Sources/Api12.swift +++ b/submodules/TelegramApi/Sources/Api12.swift @@ -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 diff --git a/submodules/TelegramApi/Sources/Api20.swift b/submodules/TelegramApi/Sources/Api20.swift index 5996ca4de7..266c1c06b9 100644 --- a/submodules/TelegramApi/Sources/Api20.swift +++ b/submodules/TelegramApi/Sources/Api20.swift @@ -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() diff --git a/submodules/TelegramApi/Sources/Api29.swift b/submodules/TelegramApi/Sources/Api29.swift index 5375b5f810..9220ea2ffa 100644 --- a/submodules/TelegramApi/Sources/Api29.swift +++ b/submodules/TelegramApi/Sources/Api29.swift @@ -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) { + 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) { let buffer = Buffer() diff --git a/submodules/TelegramApi/Sources/Api8.swift b/submodules/TelegramApi/Sources/Api8.swift index 1362907e19..95396f2b16 100644 --- a/submodules/TelegramApi/Sources/Api8.swift +++ b/submodules/TelegramApi/Sources/Api8.swift @@ -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 diff --git a/submodules/TelegramApi/Sources/Api9.swift b/submodules/TelegramApi/Sources/Api9.swift index 3abcb9bab0..0bc8e1a56e 100644 --- a/submodules/TelegramApi/Sources/Api9.swift +++ b/submodules/TelegramApi/Sources/Api9.swift @@ -677,6 +677,7 @@ public extension Api { case inputStickerSetAnimatedEmoji case inputStickerSetAnimatedEmojiAnimations case inputStickerSetDice(emoticon: String) + case inputStickerSetEmojiGenericAnimations case inputStickerSetEmpty case inputStickerSetID(id: Int64, accessHash: Int64) case inputStickerSetPremiumGifts @@ -701,6 +702,12 @@ public extension Api { buffer.appendInt32(-427863538) } serializeString(emoticon, buffer: buffer, boxed: false) + break + case .inputStickerSetEmojiGenericAnimations: + if boxed { + buffer.appendInt32(80008398) + } + break case .inputStickerSetEmpty: if boxed { @@ -738,6 +745,8 @@ public extension Api { return ("inputStickerSetAnimatedEmojiAnimations", []) case .inputStickerSetDice(let emoticon): return ("inputStickerSetDice", [("emoticon", String(describing: emoticon))]) + case .inputStickerSetEmojiGenericAnimations: + return ("inputStickerSetEmojiGenericAnimations", []) case .inputStickerSetEmpty: return ("inputStickerSetEmpty", []) case .inputStickerSetID(let id, let accessHash): @@ -766,6 +775,9 @@ public extension Api { return nil } } + public static func parse_inputStickerSetEmojiGenericAnimations(_ reader: BufferReader) -> InputStickerSet? { + return Api.InputStickerSet.inputStickerSetEmojiGenericAnimations + } public static func parse_inputStickerSetEmpty(_ reader: BufferReader) -> InputStickerSet? { return Api.InputStickerSet.inputStickerSetEmpty } diff --git a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift index 5f07721f99..bd530e6441 100644 --- a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift +++ b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift @@ -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 { diff --git a/submodules/TelegramCore/Sources/Account/AccountManager.swift b/submodules/TelegramCore/Sources/Account/AccountManager.swift index 49e753f554..af52500d12 100644 --- a/submodules/TelegramCore/Sources/Account/AccountManager.swift +++ b/submodules/TelegramCore/Sources/Account/AccountManager.swift @@ -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 }() diff --git a/submodules/TelegramCore/Sources/ApiUtils/ChatContextResult.swift b/submodules/TelegramCore/Sources/ApiUtils/ChatContextResult.swift index 4b8832f4f3..31081d4f98 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/ChatContextResult.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/ChatContextResult.swift @@ -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) } } } diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index 6427a34b34..7e2d61aead 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -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, _): diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaFile.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaFile.swift index 4cea03247a..16e478b363 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaFile.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaFile.swift @@ -64,6 +64,8 @@ extension StickerPackReference { self = .animatedEmojiAnimations case .inputStickerSetPremiumGifts: self = .premiumGifts + case .inputStickerSetEmojiGenericAnimations: + self = .premiumGifts } } } diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index f8fb74f316..e3e5775fa5 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -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)) + }) } } diff --git a/submodules/TelegramCore/Sources/State/Serialization.swift b/submodules/TelegramCore/Sources/State/Serialization.swift index 8575ec3884..b7c633834b 100644 --- a/submodules/TelegramCore/Sources/State/Serialization.swift +++ b/submodules/TelegramCore/Sources/State/Serialization.swift @@ -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! { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaInvoice.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaInvoice.swift index 8c9e8c425e..f3a5467a21 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaInvoice.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaInvoice.swift @@ -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) + } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift index da5c02c6d6..d584cd3b32 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift @@ -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: [] ) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 6d0eff7c3c..a57283e3d6 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -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 { diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 4afdfe4501..1ce39279a5 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -112,9 +112,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 { @@ -1356,7 +1360,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) diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift index 31f6b1cc3d..bcfda8f2d6 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift index 78d742ed1e..1bcfff07bf 100644 --- a/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift @@ -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 } }