From cdc82e4235b6d386fbc8adb9620925fcf762927d Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 6 Jan 2025 16:00:43 +0400 Subject: [PATCH] Update API --- .../Telegram-iOS/en.lproj/Localizable.strings | 9 + .../Sources/AccountContext.swift | 31 +- .../Sources/ShareController.swift | 55 ++++ .../Sources/ChatListController.swift | 8 +- .../Sources/Node/ChatListNoticeItem.swift | 5 +- submodules/DrawingUI/BUILD | 2 +- .../Sources/DrawingStickerEntityView.swift | 105 +++++- .../Sources/LegacyPaintStickersContext.swift | 2 +- submodules/ShareController/BUILD | 4 +- .../Sources/ShareController.swift | 22 -- submodules/StickerPackPreviewUI/BUILD | 1 - .../StickerPackPreviewController.swift | 19 +- .../Sources/StickerPackScreen.swift | 18 +- submodules/TelegramApi/Sources/Api0.swift | 10 +- submodules/TelegramApi/Sources/Api14.swift | 26 ++ submodules/TelegramApi/Sources/Api24.swift | 60 ++-- submodules/TelegramApi/Sources/Api28.swift | 22 ++ submodules/TelegramApi/Sources/Api34.swift | 90 +++--- submodules/TelegramApi/Sources/Api35.swift | 208 ++++++------ submodules/TelegramApi/Sources/Api36.swift | 172 ++++++---- submodules/TelegramApi/Sources/Api37.swift | 54 ++++ submodules/TelegramApi/Sources/Api38.swift | 32 +- .../ApiUtils/StoreMessage_Telegram.swift | 4 + .../ApiUtils/TelegramMediaWebpage.swift | 5 + .../Sources/State/Serialization.swift | 2 +- .../SyncCore_TelegramMediaWebpage.swift | 30 ++ .../TelegramEngine/Messages/MediaArea.swift | 12 + .../TelegramEngine/Payments/StarGifts.swift | 87 ++++- .../Payments/TelegramEnginePayments.swift | 4 + .../TelegramCore/Sources/WebpagePreview.swift | 54 ++-- .../Sources/CameraCodeResultComponent.swift | 3 +- .../ChatMessageGiftBubbleContentNode.swift | 138 ++++---- .../ChatMessageWebpageBubbleContentNode.swift | 2 + .../ChatRecentActionsControllerNode.swift | 2 + .../Sources/GiftItemComponent.swift | 3 +- .../Components/Gifts/GiftViewScreen/BUILD | 1 + .../Sources/ButtonsComponent.swift | 143 +++++++++ .../Sources/GiftViewScreen.swift | 300 +++++++++++------- .../Drawing/CodableDrawingEntity.swift | 5 + .../Drawing/DrawingStickerEntity.swift | 26 +- .../Sources/DrawingMessageRenderer.swift | 35 +- .../MediaEditor/Sources/MediaEditor.swift | 16 +- .../Sources/MediaEditorComposerEntity.swift | 2 +- .../Sources/MediaEditorUtils.swift | 4 +- .../Sources/MediaEditorDrafts.swift | 2 +- .../Sources/MediaEditorScreen.swift | 206 ++++++++---- ...StoryItemSetContainerViewSendMessage.swift | 31 +- .../Chat/ChatControllerOpenStorySharing.swift | 17 +- .../TelegramUI/Sources/ChatController.swift | 8 +- .../ChatControllerOpenMessageShareMenu.swift | 2 +- .../TelegramUI/Sources/OpenResolvedUrl.swift | 7 + submodules/TelegramUI/Sources/OpenUrl.swift | 16 + .../Sources/SharedAccountContext.swift | 16 +- .../UrlHandling/Sources/UrlHandling.swift | 8 + 54 files changed, 1529 insertions(+), 617 deletions(-) create mode 100644 submodules/AccountContext/Sources/ShareController.swift create mode 100644 submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/ButtonsComponent.swift diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index e952f161f6..5fccbe524c 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -13641,3 +13641,12 @@ Sorry for the inconvenience."; "Gift.View.Outgoing.NameHidden" = "Only %@ can see your name."; "Gift.View.Outgoing.NameAndMessageHidden" = "Only %@ can see your name and message."; + +"Conversation.ViewStarGift" = "VIEW COLLECTIBLE"; + +"ChatList.AddPhoto.Title" = "Add your photo! 📸"; +"ChatList.AddPhoto.Text" = "Help your friends spot you easily."; + +"Story.ViewGift" = "View Gift"; + +"Camera.OpenChat" = "Open Chat"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 1d31f0f535..01baed1ccb 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -318,6 +318,7 @@ public enum ResolvedUrl { case boost(peerId: PeerId?, status: ChannelBoostStatus?, myBoostStatus: MyBoostStatus?) case premiumGiftCode(slug: String) case premiumMultiGift(reference: String?) + case collectible(gift: StarGift.UniqueGift?) case messageLink(link: TelegramResolvedMessageLink?) } @@ -1099,10 +1100,13 @@ public protocol SharedAccountContext: AnyObject { func makeStarsGiftScreen(context: AccountContext, message: EngineMessage) -> ViewController func makeStarsGiveawayBoostScreen(context: AccountContext, peerId: EnginePeer.Id, boost: ChannelBoostersContext.State.Boost) -> ViewController func makeStarsIntroScreen(context: AccountContext) -> ViewController - func makeGiftViewScreen(context: AccountContext, message: EngineMessage) -> ViewController + func makeGiftViewScreen(context: AccountContext, message: EngineMessage, shareStory: (() -> Void)?) -> ViewController + func makeGiftViewScreen(context: AccountContext, gift: StarGift.UniqueGift, shareStory: (() -> Void)?) -> ViewController func makeContentReportScreen(context: AccountContext, subject: ReportContentSubject, forceDark: Bool, present: @escaping (ViewController) -> Void, completion: @escaping () -> Void, requestSelectMessages: ((String, Data, String?) -> Void)?) + func makeShareController(context: AccountContext, subject: ShareControllerSubject, forceExternal: Bool, shareStory: (() -> Void)?, actionCompleted: (() -> Void)?) -> ViewController + func makeMiniAppListScreenInitialData(context: AccountContext) -> Signal func makeMiniAppListScreen(context: AccountContext, initialData: MiniAppListScreenInitialData) -> ViewController @@ -1338,28 +1342,3 @@ public struct StickersSearchConfiguration { } } } - -public protocol ShareControllerAccountContext: AnyObject { - var accountId: AccountRecordId { get } - var accountPeerId: EnginePeer.Id { get } - var stateManager: AccountStateManager { get } - var engineData: TelegramEngine.EngineData { get } - var animationCache: AnimationCache { get } - var animationRenderer: MultiAnimationRenderer { get } - var contentSettings: ContentSettings { get } - var appConfiguration: AppConfiguration { get } - - func resolveInlineStickers(fileIds: [Int64]) -> Signal<[Int64: TelegramMediaFile], NoError> -} - -public protocol ShareControllerEnvironment: AnyObject { - var presentationData: PresentationData { get } - var updatedPresentationData: Signal { get } - var isMainApp: Bool { get } - var energyUsageSettings: EnergyUsageSettings { get } - - var mediaManager: MediaManager? { get } - - func setAccountUserInterfaceInUse(id: AccountRecordId) -> Disposable - func donateSendMessageIntent(account: ShareControllerAccountContext, peerIds: [EnginePeer.Id]) -} diff --git a/submodules/AccountContext/Sources/ShareController.swift b/submodules/AccountContext/Sources/ShareController.swift new file mode 100644 index 0000000000..a7cf5d763a --- /dev/null +++ b/submodules/AccountContext/Sources/ShareController.swift @@ -0,0 +1,55 @@ +import Foundation +import SwiftSignalKit +import Postbox +import TelegramCore +import TelegramPresentationData +import TelegramUIPreferences +import AnimationCache +import MultiAnimationRenderer + +public protocol ShareControllerAccountContext: AnyObject { + var accountId: AccountRecordId { get } + var accountPeerId: EnginePeer.Id { get } + var stateManager: AccountStateManager { get } + var engineData: TelegramEngine.EngineData { get } + var animationCache: AnimationCache { get } + var animationRenderer: MultiAnimationRenderer { get } + var contentSettings: ContentSettings { get } + var appConfiguration: AppConfiguration { get } + + func resolveInlineStickers(fileIds: [Int64]) -> Signal<[Int64: TelegramMediaFile], NoError> +} + +public protocol ShareControllerEnvironment: AnyObject { + var presentationData: PresentationData { get } + var updatedPresentationData: Signal { get } + var isMainApp: Bool { get } + var energyUsageSettings: EnergyUsageSettings { get } + + var mediaManager: MediaManager? { get } + + func setAccountUserInterfaceInUse(id: AccountRecordId) -> Disposable + func donateSendMessageIntent(account: ShareControllerAccountContext, peerIds: [EnginePeer.Id]) +} + +public enum ShareControllerExternalStatus { + case preparing(Bool) + case progress(Float) + case done +} + +public enum ShareControllerError { + case generic + case fileTooBig(Int64) +} + +public enum ShareControllerSubject { + case url(String) + case text(String) + case quote(text: String, url: String) + case messages([Message]) + case image([ImageRepresentationWithReference]) + case media(AnyMediaReference) + case mapMedia(TelegramMediaMap) + case fromExternal(([PeerId], [PeerId: Int64], String, ShareControllerAccountContext, Bool) -> Signal) +} diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 0dc0f80c7a..77b15690c2 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -6538,7 +6538,13 @@ private final class ChatListLocationContext { if channel.flags.contains(.requestToJoin) { actionTitle = presentationData.strings.Group_ApplyToJoin } else { - actionTitle = presentationData.strings.Channel_JoinChannel + switch channel.info { + case .broadcast: + actionTitle = presentationData.strings.Channel_JoinChannel + case .group: + actionTitle = presentationData.strings.Group_JoinGroup + } + } toolbar = Toolbar(leftAction: nil, rightAction: nil, middleAction: ToolbarAction(title: actionTitle, isEnabled: true)) } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNoticeItem.swift b/submodules/ChatListUI/Sources/Node/ChatListNoticeItem.swift index 65a0f6f330..b95b44fe7f 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNoticeItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNoticeItem.swift @@ -289,9 +289,8 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode { titleString = attributedTitle textString = NSAttributedString(string: text, font: smallTextFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor) case let .setupPhoto(accountPeer): - //TODO:localize - titleString = NSAttributedString(string: "Add your photo! 📸", font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor) - textString = NSAttributedString(string: "Help your friends spot you easily.", font: smallTextFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor) + titleString = NSAttributedString(string: item.strings.ChatList_AddPhoto_Title, font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor) + textString = NSAttributedString(string: item.strings.ChatList_AddPhoto_Text, font: smallTextFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor) avatarPeer = accountPeer } diff --git a/submodules/DrawingUI/BUILD b/submodules/DrawingUI/BUILD index 9eb89f2f76..4e444e4071 100644 --- a/submodules/DrawingUI/BUILD +++ b/submodules/DrawingUI/BUILD @@ -98,7 +98,7 @@ swift_library( "//submodules/TelegramUI/Components/LottieComponent", "//submodules/TelegramUI/Components/LottieComponentResourceContent", "//submodules/ImageTransparency", - "//submodules/GalleryUI", + #"//submodules/GalleryUI", "//submodules/MediaPlayer:UniversalMediaPlayer", "//submodules/TelegramUniversalVideoContent", "//submodules/TelegramUI/Components/CameraButtonComponent", diff --git a/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift b/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift index 99f3629b04..29f2e4ce2b 100644 --- a/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift +++ b/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift @@ -142,6 +142,8 @@ public class DrawingStickerEntityView: DrawingEntityView { return image } else if case .message = self.stickerEntity.content { return self.animatedImageView?.image + } else if case .gift = self.stickerEntity.content { + return self.animatedImageView?.image } else { return nil } @@ -167,7 +169,7 @@ public class DrawingStickerEntityView: DrawingEntityView { return file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0) case .dualVideoReference: return CGSize(width: 512.0, height: 512.0) - case let .message(_, size, _, _, _): + case let .message(_, size, _, _, _), let .gift(_, size): return size } } @@ -296,6 +298,43 @@ public class DrawingStickerEntityView: DrawingEntityView { if let file, let _ = mediaRect { self.setupWithVideo(file) } + } else if case let .gift(gift, _) = self.stickerEntity.content { + if let image = self.stickerEntity.renderImage { + self.setupWithImage(image, overlayImage: self.stickerEntity.overlayRenderImage) + } + + var file: TelegramMediaFile? + for attribute in gift.attributes { + if case let .model(_, fileValue, _) = attribute { + file = fileValue + break + } + } + guard let file, let dimensions = file.dimensions else { + return + } + if self.animationNode == nil { + let animationNode = DefaultAnimatedStickerNodeImpl() + animationNode.clipsToBounds = true + animationNode.autoplay = false + self.animationNode = animationNode + animationNode.started = { [weak self, weak animationNode] in + self?.imageNode.isHidden = true + + if let animationNode = animationNode { + let _ = (animationNode.status + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] status in + self?.started?(status.duration) + }) + } + } + self.addSubnode(self.imageNode) + self.addSubnode(animationNode) + } + self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: self.context.account.postbox, userLocation: .other, file: file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 256.0, height: 256.0)))) + self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: self.context.account, userLocation: .other, fileReference: stickerPackFileReference(file), resource: file.resource).start()) + self.setNeedsLayout() } } @@ -418,7 +457,18 @@ public class DrawingStickerEntityView: DrawingEntityView { if self.isPlaying != isPlaying { self.isPlaying = isPlaying - if let file = self.file { + var file: TelegramMediaFile? + if let fileValue = self.file { + file = fileValue + } else if case let .gift(gift, _) = self.stickerEntity.content { + for attribute in gift.attributes { + if case let .model(_, fileValue, _) = attribute { + file = fileValue + break + } + } + } + if let file { if isPlaying && !self.didSetUpAnimationNode { self.didSetUpAnimationNode = true let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512) @@ -596,15 +646,22 @@ public class DrawingStickerEntityView: DrawingEntityView { let imageSize = self.dimensions.aspectFitted(boundingSize) let imageFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: (size.height - imageSize.height) / 2.0), size: imageSize) - - self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))() - self.imageNode.frame = imageFrame + + var animationSize = CGSize(width: imageSize.width, height: imageSize.width) + var animationFrame = imageFrame + if case .gift = self.stickerEntity.content { + animationSize = CGSize(width: animationSize.width * 0.48, height: animationSize.height * 0.48) + animationFrame = CGRect(origin: CGPoint(x: floor((size.width - animationSize.width) / 2.0), y: 22.0), size: animationSize) + } + + self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: animationSize, boundingSize: animationSize, intrinsicInsets: UIEdgeInsets()))() + self.imageNode.frame = animationFrame if let animationNode = self.animationNode { if self.isReaction { - animationNode.cornerRadius = floor(imageSize.width * 0.1) + animationNode.cornerRadius = floor(animationSize.width * 0.1) } - animationNode.frame = imageFrame - animationNode.updateLayout(size: imageSize) + animationNode.frame = animationFrame + animationNode.updateLayout(size: animationSize) if !self.didApplyVisibility { self.didApplyVisibility = true @@ -818,6 +875,35 @@ public class DrawingStickerEntityView: DrawingEntityView { return entities } + } else if case let .gift(gift, _) = self.stickerEntity.content { + var file: TelegramMediaFile? + for attribute in gift.attributes { + if case let .model(_, fileValue, _) = attribute { + file = fileValue + break + } + } + if let file, let animationNode = self.animationNode { + let stickerSize = self.bounds.size + let stickerPosition = self.stickerEntity.position + let videoSize = animationNode.frame.size + let scale = self.stickerEntity.scale + let rotation = self.stickerEntity.rotation + + let videoPosition = animationNode.position.offsetBy(dx: -stickerSize.width / 2.0, dy: -stickerSize.height / 2.0) + let videoScale = videoSize.width / stickerSize.width + + let videoEntity = DrawingStickerEntity(content: .file(.standalone(media: file), .sticker)) + videoEntity.referenceDrawingSize = self.stickerEntity.referenceDrawingSize + videoEntity.position = stickerPosition.offsetBy( + dx: (videoPosition.x * cos(rotation) - videoPosition.y * sin(rotation)) * scale, + dy: (videoPosition.y * cos(rotation) + videoPosition.x * sin(rotation)) * scale + ) + videoEntity.scale = scale * videoScale + videoEntity.rotation = rotation + + return [videoEntity] + } } return [] } @@ -1103,6 +1189,9 @@ final class DrawingStickerEntitySelectionView: DrawingEntitySelectionView { if case .message = entity.content { cornerRadius *= 2.1 count = 24 + } else if case .gift = entity.content { + cornerRadius *= 2.1 + count = 24 } else if case .image = entity.content { count = 24 } diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift index 79d1597354..73f16210f2 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift @@ -147,7 +147,7 @@ private class LegacyPaintStickerEntity: LegacyPaintEntity { case let .image(image, _): self.file = nil self.imagePromise.set(.single(image)) - case .animatedImage, .video, .dualVideoReference, .message: + case .animatedImage, .video, .dualVideoReference, .message, .gift: self.file = nil } } diff --git a/submodules/ShareController/BUILD b/submodules/ShareController/BUILD index 096622d8c8..3f541667da 100644 --- a/submodules/ShareController/BUILD +++ b/submodules/ShareController/BUILD @@ -43,7 +43,9 @@ swift_library( "//submodules/Components/MultilineTextComponent", "//submodules/Components/BundleIconComponent", "//submodules/TelegramUI/Components/LottieComponent", - #"//submodules/TelegramUI/Components/MessageInputPanelComponent", + "//submodules/TelegramUI/Components/MessageInputPanelComponent", + "//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode", + "//submodules/ChatPresentationInterfaceState", ], visibility = [ "//visibility:public", diff --git a/submodules/ShareController/Sources/ShareController.swift b/submodules/ShareController/Sources/ShareController.swift index 09447ffa43..9de13e9719 100644 --- a/submodules/ShareController/Sources/ShareController.swift +++ b/submodules/ShareController/Sources/ShareController.swift @@ -40,17 +40,6 @@ public enum ShareControllerPreferredAction { case custom(action: ShareControllerAction) } -public enum ShareControllerExternalStatus { - case preparing(Bool) - case progress(Float) - case done -} - -public enum ShareControllerError { - case generic - case fileTooBig(Int64) -} - public struct ShareControllerSegmentedValue { let title: String let subject: ShareControllerSubject @@ -65,17 +54,6 @@ public struct ShareControllerSegmentedValue { } } -public enum ShareControllerSubject { - case url(String) - case text(String) - case quote(text: String, url: String) - case messages([Message]) - case image([ImageRepresentationWithReference]) - case media(AnyMediaReference) - case mapMedia(TelegramMediaMap) - case fromExternal(([PeerId], [PeerId: Int64], String, ShareControllerAccountContext, Bool) -> Signal) -} - private enum ExternalShareItem { case text(String) case url(URL) diff --git a/submodules/StickerPackPreviewUI/BUILD b/submodules/StickerPackPreviewUI/BUILD index 00654a3f56..1d935d2ea6 100644 --- a/submodules/StickerPackPreviewUI/BUILD +++ b/submodules/StickerPackPreviewUI/BUILD @@ -18,7 +18,6 @@ swift_library( "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/AccountContext:AccountContext", "//submodules/TelegramUIPreferences:TelegramUIPreferences", - "//submodules/ShareController:ShareController", "//submodules/StickerResources:StickerResources", "//submodules/AlertUI:AlertUI", "//submodules/PresentationDataUtils:PresentationDataUtils", diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewController.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewController.swift index c788f58895..902f14b131 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewController.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewController.swift @@ -7,7 +7,6 @@ import TelegramCore import SwiftSignalKit import TelegramUIPreferences import AccountContext -import ShareController import StickerResources import AlertUI import PresentationDataUtils @@ -116,15 +115,19 @@ public final class StickerPackPreviewController: ViewController, StandalonePrese } if let stickerPackContentsValue = strongSelf.stickerPackContentsValue, case let .result(info, _, _) = stickerPackContentsValue, !info.shortName.isEmpty { - let shareController = ShareController(context: strongSelf.context, subject: .url("https://t.me/addstickers/\(info.shortName)"), externalShare: true) - let parentNavigationController = strongSelf.parentNavigationController - shareController.actionCompleted = { [weak parentNavigationController] in - if let parentNavigationController = parentNavigationController, let controller = parentNavigationController.topViewController as? ViewController { - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - controller.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) + let shareController = strongSelf.context.sharedContext.makeShareController( + context: strongSelf.context, + subject: .url("https://t.me/addstickers/\(info.shortName)"), + forceExternal: true, + shareStory: nil, + actionCompleted: { [weak parentNavigationController] in + if let parentNavigationController = parentNavigationController, let controller = parentNavigationController.topViewController as? ViewController { + let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } + controller.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) + } } - } + ) strongSelf.present(shareController, in: .window(.root)) strongSelf.dismiss() } diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift index b66cafaad3..e1689b37bb 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift @@ -13,7 +13,6 @@ import ShimmerEffect import ContextUI import MoreButtonNode import UndoUI -import ShareController import TextFormat import PremiumUI import OverlayStatusController @@ -1133,13 +1132,18 @@ private final class StickerPackContainer: ASDisplayNode { if let strongSelf = self { let parentNavigationController = strongSelf.controller?.parentNavigationController - let shareController = ShareController(context: strongSelf.context, subject: shareSubject) - shareController.actionCompleted = { [weak parentNavigationController] in - if let parentNavigationController = parentNavigationController, let controller = parentNavigationController.topViewController as? ViewController { - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - controller.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) + let shareController = strongSelf.context.sharedContext.makeShareController( + context: strongSelf.context, + subject: shareSubject, + forceExternal: false, + shareStory: nil, + actionCompleted: { [weak parentNavigationController] in + if let parentNavigationController = parentNavigationController, let controller = parentNavigationController.topViewController as? ViewController { + let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } + controller.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) + } } - } + ) strongSelf.controller?.present(shareController, in: .window(.root)) } }))) diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index b18395f5ec..1d79f7b5d0 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -534,6 +534,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1300094593] = { return Api.MediaArea.parse_inputMediaAreaVenue($0) } dict[1996756655] = { return Api.MediaArea.parse_mediaAreaChannelPost($0) } dict[-891992787] = { return Api.MediaArea.parse_mediaAreaGeoPoint($0) } + dict[1468491885] = { return Api.MediaArea.parse_mediaAreaStarGift($0) } dict[340088945] = { return Api.MediaArea.parse_mediaAreaSuggestedReaction($0) } dict[926421125] = { return Api.MediaArea.parse_mediaAreaUrl($0) } dict[-1098720356] = { return Api.MediaArea.parse_mediaAreaVenue($0) } @@ -909,7 +910,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1301522832] = { return Api.SponsoredMessage.parse_sponsoredMessage($0) } dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) } dict[46953416] = { return Api.StarGift.parse_starGift($0) } - dict[1779697613] = { return Api.StarGift.parse_starGiftUnique($0) } + dict[880997154] = { return Api.StarGift.parse_starGiftUnique($0) } dict[-1809377438] = { return Api.StarGiftAttribute.parse_starGiftAttributeBackdrop($0) } dict[970559507] = { return Api.StarGiftAttribute.parse_starGiftAttributeModel($0) } dict[-1070837941] = { return Api.StarGiftAttribute.parse_starGiftAttributeOriginalDetails($0) } @@ -1156,6 +1157,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1355547603] = { return Api.WebPageAttribute.parse_webPageAttributeStickerSet($0) } dict[781501415] = { return Api.WebPageAttribute.parse_webPageAttributeStory($0) } dict[1421174295] = { return Api.WebPageAttribute.parse_webPageAttributeTheme($0) } + dict[-814781000] = { return Api.WebPageAttribute.parse_webPageAttributeUniqueStarGift($0) } dict[211046684] = { return Api.WebViewMessageSent.parse_webViewMessageSent($0) } dict[1294139288] = { return Api.WebViewResult.parse_webViewResultUrl($0) } dict[-1389486888] = { return Api.account.AuthorizationForm.parse_authorizationForm($0) } @@ -1361,6 +1363,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[870003448] = { return Api.messages.TranslatedText.parse_translateResult($0) } dict[1218005070] = { return Api.messages.VotesList.parse_votesList($0) } dict[-44166467] = { return Api.messages.WebPage.parse_webPage($0) } + dict[-1254192351] = { return Api.messages.WebPagePreview.parse_webPagePreview($0) } dict[1042605427] = { return Api.payments.BankCardData.parse_bankCardData($0) } dict[675942550] = { return Api.payments.CheckedGiftCode.parse_checkedGiftCode($0) } dict[-1730811363] = { return Api.payments.ConnectedStarRefBots.parse_connectedStarRefBots($0) } @@ -1383,6 +1386,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[497778871] = { return Api.payments.StarsRevenueWithdrawalUrl.parse_starsRevenueWithdrawalUrl($0) } dict[1822222573] = { return Api.payments.StarsStatus.parse_starsStatus($0) } dict[-1261053863] = { return Api.payments.SuggestedStarRefBots.parse_suggestedStarRefBots($0) } + dict[-895289845] = { return Api.payments.UniqueStarGift.parse_uniqueStarGift($0) } dict[1801827607] = { return Api.payments.UserStarGifts.parse_userStarGifts($0) } dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) } dict[541839704] = { return Api.phone.ExportedGroupCallInvite.parse_exportedGroupCallInvite($0) } @@ -2440,6 +2444,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.messages.WebPage: _1.serialize(buffer, boxed) + case let _1 as Api.messages.WebPagePreview: + _1.serialize(buffer, boxed) case let _1 as Api.payments.BankCardData: _1.serialize(buffer, boxed) case let _1 as Api.payments.CheckedGiftCode: @@ -2472,6 +2478,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.payments.SuggestedStarRefBots: _1.serialize(buffer, boxed) + case let _1 as Api.payments.UniqueStarGift: + _1.serialize(buffer, boxed) case let _1 as Api.payments.UserStarGifts: _1.serialize(buffer, boxed) case let _1 as Api.payments.ValidatedRequestedInfo: diff --git a/submodules/TelegramApi/Sources/Api14.swift b/submodules/TelegramApi/Sources/Api14.swift index aaba39c1cd..b1d5202b08 100644 --- a/submodules/TelegramApi/Sources/Api14.swift +++ b/submodules/TelegramApi/Sources/Api14.swift @@ -868,6 +868,7 @@ public extension Api { case inputMediaAreaVenue(coordinates: Api.MediaAreaCoordinates, queryId: Int64, resultId: String) case mediaAreaChannelPost(coordinates: Api.MediaAreaCoordinates, channelId: Int64, msgId: Int32) case mediaAreaGeoPoint(flags: Int32, coordinates: Api.MediaAreaCoordinates, geo: Api.GeoPoint, address: Api.GeoPointAddress?) + case mediaAreaStarGift(coordinates: Api.MediaAreaCoordinates, slug: String) case mediaAreaSuggestedReaction(flags: Int32, coordinates: Api.MediaAreaCoordinates, reaction: Api.Reaction) case mediaAreaUrl(coordinates: Api.MediaAreaCoordinates, url: String) case mediaAreaVenue(coordinates: Api.MediaAreaCoordinates, geo: Api.GeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String) @@ -908,6 +909,13 @@ public extension Api { geo.serialize(buffer, true) if Int(flags) & Int(1 << 0) != 0 {address!.serialize(buffer, true)} break + case .mediaAreaStarGift(let coordinates, let slug): + if boxed { + buffer.appendInt32(1468491885) + } + coordinates.serialize(buffer, true) + serializeString(slug, buffer: buffer, boxed: false) + break case .mediaAreaSuggestedReaction(let flags, let coordinates, let reaction): if boxed { buffer.appendInt32(340088945) @@ -957,6 +965,8 @@ public extension Api { return ("mediaAreaChannelPost", [("coordinates", coordinates as Any), ("channelId", channelId as Any), ("msgId", msgId as Any)]) case .mediaAreaGeoPoint(let flags, let coordinates, let geo, let address): return ("mediaAreaGeoPoint", [("flags", flags as Any), ("coordinates", coordinates as Any), ("geo", geo as Any), ("address", address as Any)]) + case .mediaAreaStarGift(let coordinates, let slug): + return ("mediaAreaStarGift", [("coordinates", coordinates as Any), ("slug", slug as Any)]) case .mediaAreaSuggestedReaction(let flags, let coordinates, let reaction): return ("mediaAreaSuggestedReaction", [("flags", flags as Any), ("coordinates", coordinates as Any), ("reaction", reaction as Any)]) case .mediaAreaUrl(let coordinates, let url): @@ -1053,6 +1063,22 @@ public extension Api { return nil } } + public static func parse_mediaAreaStarGift(_ reader: BufferReader) -> MediaArea? { + var _1: Api.MediaAreaCoordinates? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.MediaAreaCoordinates + } + var _2: String? + _2 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MediaArea.mediaAreaStarGift(coordinates: _1!, slug: _2!) + } + else { + return nil + } + } public static func parse_mediaAreaSuggestedReaction(_ reader: BufferReader) -> MediaArea? { var _1: Int32? _1 = reader.readInt32() diff --git a/submodules/TelegramApi/Sources/Api24.swift b/submodules/TelegramApi/Sources/Api24.swift index 51a39f1c74..6a393c61c1 100644 --- a/submodules/TelegramApi/Sources/Api24.swift +++ b/submodules/TelegramApi/Sources/Api24.swift @@ -575,7 +575,7 @@ public extension Api { public extension Api { enum StarGift: TypeConstructorDescription { case starGift(flags: Int32, id: Int64, sticker: Api.Document, stars: Int64, availabilityRemains: Int32?, availabilityTotal: Int32?, convertStars: Int64, firstSaleDate: Int32?, lastSaleDate: Int32?, upgradeStars: Int64?) - case starGiftUnique(id: Int64, title: String, num: Int32, ownerId: Int64, attributes: [Api.StarGiftAttribute], availabilityIssued: Int32, availabilityTotal: Int32) + case starGiftUnique(flags: Int32, id: Int64, title: String, slug: String, num: Int32, ownerId: Int64?, ownerName: String?, attributes: [Api.StarGiftAttribute], availabilityIssued: Int32, availabilityTotal: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -594,14 +594,17 @@ public extension Api { if Int(flags) & Int(1 << 1) != 0 {serializeInt32(lastSaleDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 3) != 0 {serializeInt64(upgradeStars!, buffer: buffer, boxed: false)} break - case .starGiftUnique(let id, let title, let num, let ownerId, let attributes, let availabilityIssued, let availabilityTotal): + case .starGiftUnique(let flags, let id, let title, let slug, let num, let ownerId, let ownerName, let attributes, let availabilityIssued, let availabilityTotal): if boxed { - buffer.appendInt32(1779697613) + buffer.appendInt32(880997154) } + serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) serializeString(title, buffer: buffer, boxed: false) + serializeString(slug, buffer: buffer, boxed: false) serializeInt32(num, buffer: buffer, boxed: false) - serializeInt64(ownerId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt64(ownerId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(ownerName!, buffer: buffer, boxed: false)} buffer.appendInt32(481674261) buffer.appendInt32(Int32(attributes.count)) for item in attributes { @@ -617,8 +620,8 @@ public extension Api { switch self { case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars): return ("starGift", [("flags", flags as Any), ("id", id as Any), ("sticker", sticker as Any), ("stars", stars as Any), ("availabilityRemains", availabilityRemains as Any), ("availabilityTotal", availabilityTotal as Any), ("convertStars", convertStars as Any), ("firstSaleDate", firstSaleDate as Any), ("lastSaleDate", lastSaleDate as Any), ("upgradeStars", upgradeStars as Any)]) - case .starGiftUnique(let id, let title, let num, let ownerId, let attributes, let availabilityIssued, let availabilityTotal): - return ("starGiftUnique", [("id", id as Any), ("title", title as Any), ("num", num as Any), ("ownerId", ownerId as Any), ("attributes", attributes as Any), ("availabilityIssued", availabilityIssued as Any), ("availabilityTotal", availabilityTotal as Any)]) + case .starGiftUnique(let flags, let id, let title, let slug, let num, let ownerId, let ownerName, let attributes, let availabilityIssued, let availabilityTotal): + return ("starGiftUnique", [("flags", flags as Any), ("id", id as Any), ("title", title as Any), ("slug", slug as Any), ("num", num as Any), ("ownerId", ownerId as Any), ("ownerName", ownerName as Any), ("attributes", attributes as Any), ("availabilityIssued", availabilityIssued as Any), ("availabilityTotal", availabilityTotal as Any)]) } } @@ -663,31 +666,40 @@ public extension Api { } } public static func parse_starGiftUnique(_ reader: BufferReader) -> StarGift? { - var _1: Int64? - _1 = reader.readInt64() - var _2: String? - _2 = parseString(reader) - var _3: Int32? - _3 = reader.readInt32() - var _4: Int64? - _4 = reader.readInt64() - var _5: [Api.StarGiftAttribute]? + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: Int64? + if Int(_1!) & Int(1 << 0) != 0 {_6 = reader.readInt64() } + var _7: String? + if Int(_1!) & Int(1 << 1) != 0 {_7 = parseString(reader) } + var _8: [Api.StarGiftAttribute]? if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftAttribute.self) + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftAttribute.self) } - var _6: Int32? - _6 = reader.readInt32() - var _7: Int32? - _7 = reader.readInt32() + var _9: Int32? + _9 = reader.readInt32() + var _10: Int32? + _10 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.StarGift.starGiftUnique(id: _1!, title: _2!, num: _3!, ownerId: _4!, attributes: _5!, availabilityIssued: _6!, availabilityTotal: _7!) + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + let _c10 = _10 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { + return Api.StarGift.starGiftUnique(flags: _1!, id: _2!, title: _3!, slug: _4!, num: _5!, ownerId: _6, ownerName: _7, attributes: _8!, availabilityIssued: _9!, availabilityTotal: _10!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api28.swift b/submodules/TelegramApi/Sources/Api28.swift index 8034ebaa39..3f9b6d17be 100644 --- a/submodules/TelegramApi/Sources/Api28.swift +++ b/submodules/TelegramApi/Sources/Api28.swift @@ -207,6 +207,7 @@ public extension Api { case webPageAttributeStickerSet(flags: Int32, stickers: [Api.Document]) case webPageAttributeStory(flags: Int32, peer: Api.Peer, id: Int32, story: Api.StoryItem?) case webPageAttributeTheme(flags: Int32, documents: [Api.Document]?, settings: Api.ThemeSettings?) + case webPageAttributeUniqueStarGift(gift: Api.StarGift) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -242,6 +243,12 @@ public extension Api { }} if Int(flags) & Int(1 << 1) != 0 {settings!.serialize(buffer, true)} break + case .webPageAttributeUniqueStarGift(let gift): + if boxed { + buffer.appendInt32(-814781000) + } + gift.serialize(buffer, true) + break } } @@ -253,6 +260,8 @@ public extension Api { return ("webPageAttributeStory", [("flags", flags as Any), ("peer", peer as Any), ("id", id as Any), ("story", story as Any)]) case .webPageAttributeTheme(let flags, let documents, let settings): return ("webPageAttributeTheme", [("flags", flags as Any), ("documents", documents as Any), ("settings", settings as Any)]) + case .webPageAttributeUniqueStarGift(let gift): + return ("webPageAttributeUniqueStarGift", [("gift", gift as Any)]) } } @@ -317,6 +326,19 @@ public extension Api { return nil } } + public static func parse_webPageAttributeUniqueStarGift(_ reader: BufferReader) -> WebPageAttribute? { + var _1: Api.StarGift? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.StarGift + } + let _c1 = _1 != nil + if _c1 { + return Api.WebPageAttribute.webPageAttributeUniqueStarGift(gift: _1!) + } + else { + return nil + } + } } } diff --git a/submodules/TelegramApi/Sources/Api34.swift b/submodules/TelegramApi/Sources/Api34.swift index 7906a60dfa..b82d6421c4 100644 --- a/submodules/TelegramApi/Sources/Api34.swift +++ b/submodules/TelegramApi/Sources/Api34.swift @@ -744,6 +744,54 @@ public extension Api.messages { } } +public extension Api.messages { + indirect enum WebPagePreview: TypeConstructorDescription { + case webPagePreview(media: Api.MessageMedia, users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .webPagePreview(let media, let users): + if boxed { + buffer.appendInt32(-1254192351) + } + media.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .webPagePreview(let media, let users): + return ("webPagePreview", [("media", media as Any), ("users", users as Any)]) + } + } + + public static func parse_webPagePreview(_ reader: BufferReader) -> WebPagePreview? { + var _1: Api.MessageMedia? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.MessageMedia + } + var _2: [Api.User]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.WebPagePreview.webPagePreview(media: _1!, users: _2!) + } + else { + return nil + } + } + + } +} public extension Api.payments { enum BankCardData: TypeConstructorDescription { case bankCardData(title: String, openUrls: [Api.BankCardOpenUrl]) @@ -1538,45 +1586,3 @@ public extension Api.payments { } } -public extension Api.payments { - enum StarGiftUpgradePreview: TypeConstructorDescription { - case starGiftUpgradePreview(sampleAttributes: [Api.StarGiftAttribute]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starGiftUpgradePreview(let sampleAttributes): - if boxed { - buffer.appendInt32(377215243) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(sampleAttributes.count)) - for item in sampleAttributes { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .starGiftUpgradePreview(let sampleAttributes): - return ("starGiftUpgradePreview", [("sampleAttributes", sampleAttributes as Any)]) - } - } - - public static func parse_starGiftUpgradePreview(_ reader: BufferReader) -> StarGiftUpgradePreview? { - var _1: [Api.StarGiftAttribute]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftAttribute.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.payments.StarGiftUpgradePreview.starGiftUpgradePreview(sampleAttributes: _1!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api35.swift b/submodules/TelegramApi/Sources/Api35.swift index a37ca076a3..c6df005355 100644 --- a/submodules/TelegramApi/Sources/Api35.swift +++ b/submodules/TelegramApi/Sources/Api35.swift @@ -1,3 +1,45 @@ +public extension Api.payments { + enum StarGiftUpgradePreview: TypeConstructorDescription { + case starGiftUpgradePreview(sampleAttributes: [Api.StarGiftAttribute]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starGiftUpgradePreview(let sampleAttributes): + if boxed { + buffer.appendInt32(377215243) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(sampleAttributes.count)) + for item in sampleAttributes { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .starGiftUpgradePreview(let sampleAttributes): + return ("starGiftUpgradePreview", [("sampleAttributes", sampleAttributes as Any)]) + } + } + + public static func parse_starGiftUpgradePreview(_ reader: BufferReader) -> StarGiftUpgradePreview? { + var _1: [Api.StarGiftAttribute]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftAttribute.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.payments.StarGiftUpgradePreview.starGiftUpgradePreview(sampleAttributes: _1!) + } + else { + return nil + } + } + + } +} public extension Api.payments { enum StarGifts: TypeConstructorDescription { case starGifts(hash: Int32, gifts: [Api.StarGift]) @@ -334,6 +376,54 @@ public extension Api.payments { } } +public extension Api.payments { + enum UniqueStarGift: TypeConstructorDescription { + case uniqueStarGift(gift: Api.StarGift, users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .uniqueStarGift(let gift, let users): + if boxed { + buffer.appendInt32(-895289845) + } + gift.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .uniqueStarGift(let gift, let users): + return ("uniqueStarGift", [("gift", gift as Any), ("users", users as Any)]) + } + } + + public static func parse_uniqueStarGift(_ reader: BufferReader) -> UniqueStarGift? { + var _1: Api.StarGift? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.StarGift + } + var _2: [Api.User]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.payments.UniqueStarGift.uniqueStarGift(gift: _1!, users: _2!) + } + else { + return nil + } + } + + } +} public extension Api.payments { enum UserStarGifts: TypeConstructorDescription { case userStarGifts(flags: Int32, count: Int32, gifts: [Api.UserStarGift], nextOffset: String?, users: [Api.User]) @@ -1776,121 +1866,3 @@ public extension Api.stats { } } -public extension Api.stats { - enum PublicForwards: TypeConstructorDescription { - case publicForwards(flags: Int32, count: Int32, forwards: [Api.PublicForward], nextOffset: String?, chats: [Api.Chat], users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .publicForwards(let flags, let count, let forwards, let nextOffset, let chats, let users): - if boxed { - buffer.appendInt32(-1828487648) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(forwards.count)) - for item in forwards { - item.serialize(buffer, true) - } - if Int(flags) & Int(1 << 0) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)} - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .publicForwards(let flags, let count, let forwards, let nextOffset, let chats, let users): - return ("publicForwards", [("flags", flags as Any), ("count", count as Any), ("forwards", forwards as Any), ("nextOffset", nextOffset as Any), ("chats", chats as Any), ("users", users as Any)]) - } - } - - public static func parse_publicForwards(_ reader: BufferReader) -> PublicForwards? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: [Api.PublicForward]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PublicForward.self) - } - var _4: String? - if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) } - var _5: [Api.Chat]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _6: [Api.User]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.stats.PublicForwards.publicForwards(flags: _1!, count: _2!, forwards: _3!, nextOffset: _4, chats: _5!, users: _6!) - } - else { - return nil - } - } - - } -} -public extension Api.stats { - enum StoryStats: TypeConstructorDescription { - case storyStats(viewsGraph: Api.StatsGraph, reactionsByEmotionGraph: Api.StatsGraph) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .storyStats(let viewsGraph, let reactionsByEmotionGraph): - if boxed { - buffer.appendInt32(1355613820) - } - viewsGraph.serialize(buffer, true) - reactionsByEmotionGraph.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .storyStats(let viewsGraph, let reactionsByEmotionGraph): - return ("storyStats", [("viewsGraph", viewsGraph as Any), ("reactionsByEmotionGraph", reactionsByEmotionGraph as Any)]) - } - } - - public static func parse_storyStats(_ reader: BufferReader) -> StoryStats? { - var _1: Api.StatsGraph? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _2: Api.StatsGraph? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.stats.StoryStats.storyStats(viewsGraph: _1!, reactionsByEmotionGraph: _2!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api36.swift b/submodules/TelegramApi/Sources/Api36.swift index fd5e3e5fa8..3d6117bab4 100644 --- a/submodules/TelegramApi/Sources/Api36.swift +++ b/submodules/TelegramApi/Sources/Api36.swift @@ -1,3 +1,121 @@ +public extension Api.stats { + enum PublicForwards: TypeConstructorDescription { + case publicForwards(flags: Int32, count: Int32, forwards: [Api.PublicForward], nextOffset: String?, chats: [Api.Chat], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .publicForwards(let flags, let count, let forwards, let nextOffset, let chats, let users): + if boxed { + buffer.appendInt32(-1828487648) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(forwards.count)) + for item in forwards { + item.serialize(buffer, true) + } + if Int(flags) & Int(1 << 0) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)} + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .publicForwards(let flags, let count, let forwards, let nextOffset, let chats, let users): + return ("publicForwards", [("flags", flags as Any), ("count", count as Any), ("forwards", forwards as Any), ("nextOffset", nextOffset as Any), ("chats", chats as Any), ("users", users as Any)]) + } + } + + public static func parse_publicForwards(_ reader: BufferReader) -> PublicForwards? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: [Api.PublicForward]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PublicForward.self) + } + var _4: String? + if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) } + var _5: [Api.Chat]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _6: [Api.User]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.stats.PublicForwards.publicForwards(flags: _1!, count: _2!, forwards: _3!, nextOffset: _4, chats: _5!, users: _6!) + } + else { + return nil + } + } + + } +} +public extension Api.stats { + enum StoryStats: TypeConstructorDescription { + case storyStats(viewsGraph: Api.StatsGraph, reactionsByEmotionGraph: Api.StatsGraph) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .storyStats(let viewsGraph, let reactionsByEmotionGraph): + if boxed { + buffer.appendInt32(1355613820) + } + viewsGraph.serialize(buffer, true) + reactionsByEmotionGraph.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .storyStats(let viewsGraph, let reactionsByEmotionGraph): + return ("storyStats", [("viewsGraph", viewsGraph as Any), ("reactionsByEmotionGraph", reactionsByEmotionGraph as Any)]) + } + } + + public static func parse_storyStats(_ reader: BufferReader) -> StoryStats? { + var _1: Api.StatsGraph? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _2: Api.StatsGraph? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.stats.StoryStats.storyStats(viewsGraph: _1!, reactionsByEmotionGraph: _2!) + } + else { + return nil + } + } + + } +} public extension Api.stickers { enum SuggestedShortName: TypeConstructorDescription { case suggestedShortName(shortName: String) @@ -1276,57 +1394,3 @@ public extension Api.upload { } } -public extension Api.upload { - enum WebFile: TypeConstructorDescription { - case webFile(size: Int32, mimeType: String, fileType: Api.storage.FileType, mtime: Int32, bytes: Buffer) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .webFile(let size, let mimeType, let fileType, let mtime, let bytes): - if boxed { - buffer.appendInt32(568808380) - } - serializeInt32(size, buffer: buffer, boxed: false) - serializeString(mimeType, buffer: buffer, boxed: false) - fileType.serialize(buffer, true) - serializeInt32(mtime, buffer: buffer, boxed: false) - serializeBytes(bytes, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .webFile(let size, let mimeType, let fileType, let mtime, let bytes): - return ("webFile", [("size", size as Any), ("mimeType", mimeType as Any), ("fileType", fileType as Any), ("mtime", mtime as Any), ("bytes", bytes as Any)]) - } - } - - public static func parse_webFile(_ reader: BufferReader) -> WebFile? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Api.storage.FileType? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.storage.FileType - } - var _4: Int32? - _4 = reader.readInt32() - var _5: Buffer? - _5 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.upload.WebFile.webFile(size: _1!, mimeType: _2!, fileType: _3!, mtime: _4!, bytes: _5!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api37.swift b/submodules/TelegramApi/Sources/Api37.swift index a5d3434758..04e18d918c 100644 --- a/submodules/TelegramApi/Sources/Api37.swift +++ b/submodules/TelegramApi/Sources/Api37.swift @@ -1,3 +1,57 @@ +public extension Api.upload { + enum WebFile: TypeConstructorDescription { + case webFile(size: Int32, mimeType: String, fileType: Api.storage.FileType, mtime: Int32, bytes: Buffer) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .webFile(let size, let mimeType, let fileType, let mtime, let bytes): + if boxed { + buffer.appendInt32(568808380) + } + serializeInt32(size, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + fileType.serialize(buffer, true) + serializeInt32(mtime, buffer: buffer, boxed: false) + serializeBytes(bytes, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .webFile(let size, let mimeType, let fileType, let mtime, let bytes): + return ("webFile", [("size", size as Any), ("mimeType", mimeType as Any), ("fileType", fileType as Any), ("mtime", mtime as Any), ("bytes", bytes as Any)]) + } + } + + public static func parse_webFile(_ reader: BufferReader) -> WebFile? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Api.storage.FileType? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.storage.FileType + } + var _4: Int32? + _4 = reader.readInt32() + var _5: Buffer? + _5 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.upload.WebFile.webFile(size: _1!, mimeType: _2!, fileType: _3!, mtime: _4!, bytes: _5!) + } + else { + return nil + } + } + + } +} public extension Api.users { enum UserFull: TypeConstructorDescription { case userFull(fullUser: Api.UserFull, chats: [Api.Chat], users: [Api.User]) diff --git a/submodules/TelegramApi/Sources/Api38.swift b/submodules/TelegramApi/Sources/Api38.swift index 9df97e6f8b..98b621dcef 100644 --- a/submodules/TelegramApi/Sources/Api38.swift +++ b/submodules/TelegramApi/Sources/Api38.swift @@ -2383,12 +2383,11 @@ public extension Api.functions.bots { } } public extension Api.functions.bots { - static func getBotRecommendations(flags: Int32, bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func getBotRecommendations(bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(676707937) - serializeInt32(flags, buffer: buffer, boxed: false) + buffer.appendInt32(-1581840363) bot.serialize(buffer, true) - return (FunctionDescription(name: "bots.getBotRecommendations", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.users.Users? in + return (FunctionDescription(name: "bots.getBotRecommendations", parameters: [("bot", String(describing: bot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.users.Users? in let reader = BufferReader(buffer) var result: Api.users.Users? if let signature = reader.readInt32() { @@ -7063,9 +7062,9 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func getWebPagePreview(flags: Int32, message: String, entities: [Api.MessageEntity]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func getWebPagePreview(flags: Int32, message: String, entities: [Api.MessageEntity]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-1956073268) + buffer.appendInt32(1460498287) serializeInt32(flags, buffer: buffer, boxed: false) serializeString(message, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) @@ -7073,11 +7072,11 @@ public extension Api.functions.messages { for item in entities! { item.serialize(buffer, true) }} - return (FunctionDescription(name: "messages.getWebPagePreview", parameters: [("flags", String(describing: flags)), ("message", String(describing: message)), ("entities", String(describing: entities))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.MessageMedia? in + return (FunctionDescription(name: "messages.getWebPagePreview", parameters: [("flags", String(describing: flags)), ("message", String(describing: message)), ("entities", String(describing: entities))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.WebPagePreview? in let reader = BufferReader(buffer) - var result: Api.MessageMedia? + var result: Api.messages.WebPagePreview? if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.MessageMedia + result = Api.parse(reader, signature: signature) as? Api.messages.WebPagePreview } return result }) @@ -9479,6 +9478,21 @@ public extension Api.functions.payments { }) } } +public extension Api.functions.payments { + static func getUniqueStarGift(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1583919758) + serializeString(slug, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.getUniqueStarGift", parameters: [("slug", String(describing: slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.UniqueStarGift? in + let reader = BufferReader(buffer) + var result: Api.payments.UniqueStarGift? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.UniqueStarGift + } + return result + }) + } +} public extension Api.functions.payments { static func getUserStarGift(msgId: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index d4ea6fa3a3..49c4cdc791 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -542,6 +542,8 @@ func mediaAreaFromApiMediaArea(_ mediaArea: Api.MediaArea) -> MediaArea? { return .channelMessage(coordinates: coodinatesFromApiMediaAreaCoordinates(coordinates), messageId: EngineMessage.Id(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), namespace: Namespaces.Message.Cloud, id: messageId)) case let .mediaAreaWeather(coordinates, emoji, temperatureC, color): return .weather(coordinates: coodinatesFromApiMediaAreaCoordinates(coordinates), emoji: emoji, temperature: temperatureC, color: color) + case let .mediaAreaStarGift(coordinates, slug): + return .starGift(coordinates: coodinatesFromApiMediaAreaCoordinates(coordinates), slug: slug) } } @@ -596,6 +598,8 @@ func apiMediaAreasFromMediaAreas(_ mediaAreas: [MediaArea], transaction: Transac apiMediaAreas.append(.mediaAreaUrl(coordinates: inputCoordinates, url: url)) case let .weather(_, emoji, temperature, color): apiMediaAreas.append(.mediaAreaWeather(coordinates: inputCoordinates, emoji: emoji, temperatureC: temperature, color: color)) + case let .starGift(_, slug): + apiMediaAreas.append(.mediaAreaStarGift(coordinates: inputCoordinates, slug: slug)) } } return apiMediaAreas diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaWebpage.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaWebpage.swift index a27d6773f8..3fc7a03776 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaWebpage.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaWebpage.swift @@ -23,6 +23,11 @@ func telegramMediaWebpageAttributeFromApiWebpageAttribute(_ attribute: Api.WebPa var files: [TelegramMediaFile] = [] files = stickers.compactMap { telegramMediaFileFromApiDocument($0, altDocuments: []) } return .stickerPack(TelegramMediaWebpageStickerPackAttribute(flags: flags, files: files)) + case let .webPageAttributeUniqueStarGift(gift): + if let starGift = StarGift(apiStarGift: gift) { + return .starGift(TelegramMediaWebpageStarGiftAttribute(gift: starGift)) + } + return nil case .webPageAttributeStory: return nil } diff --git a/submodules/TelegramCore/Sources/State/Serialization.swift b/submodules/TelegramCore/Sources/State/Serialization.swift index cbbbced361..15c02dcff4 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 196 + return 197 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaWebpage.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaWebpage.swift index de0cecfa5f..7d792980d9 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaWebpage.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaWebpage.swift @@ -4,12 +4,14 @@ private enum TelegramMediaWebpageAttributeTypes: Int32 { case unsupported case theme case stickerPack + case starGift } public enum TelegramMediaWebpageAttribute: PostboxCoding, Equatable { case unsupported case theme(TelegraMediaWebpageThemeAttribute) case stickerPack(TelegramMediaWebpageStickerPackAttribute) + case starGift(TelegramMediaWebpageStarGiftAttribute) public init(decoder: PostboxDecoder) { switch decoder.decodeInt32ForKey("r", orElse: 0) { @@ -17,6 +19,8 @@ public enum TelegramMediaWebpageAttribute: PostboxCoding, Equatable { self = .theme(decoder.decodeObjectForKey("a", decoder: { TelegraMediaWebpageThemeAttribute(decoder: $0) }) as! TelegraMediaWebpageThemeAttribute) case TelegramMediaWebpageAttributeTypes.stickerPack.rawValue: self = .stickerPack(decoder.decodeObjectForKey("a", decoder: { TelegramMediaWebpageStickerPackAttribute(decoder: $0) }) as! TelegramMediaWebpageStickerPackAttribute) + case TelegramMediaWebpageAttributeTypes.starGift.rawValue: + self = .starGift(decoder.decodeObjectForKey("a", decoder: { TelegramMediaWebpageStarGiftAttribute(decoder: $0) }) as! TelegramMediaWebpageStarGiftAttribute) default: self = .unsupported } @@ -32,6 +36,9 @@ public enum TelegramMediaWebpageAttribute: PostboxCoding, Equatable { case let .stickerPack(attribute): encoder.encodeInt32(TelegramMediaWebpageAttributeTypes.stickerPack.rawValue, forKey: "r") encoder.encodeObject(attribute, forKey: "a") + case let .starGift(attribute): + encoder.encodeInt32(TelegramMediaWebpageAttributeTypes.starGift.rawValue, forKey: "r") + encoder.encodeObject(attribute, forKey: "a") } } } @@ -127,6 +134,29 @@ public final class TelegramMediaWebpageStickerPackAttribute: PostboxCoding, Equa } } +public final class TelegramMediaWebpageStarGiftAttribute: PostboxCoding, Equatable { + public static func == (lhs: TelegramMediaWebpageStarGiftAttribute, rhs: TelegramMediaWebpageStarGiftAttribute) -> Bool { + if lhs.gift != rhs.gift { + return false + } + return true + } + + public let gift: StarGift + + public init(gift: StarGift) { + self.gift = gift + } + + public init(decoder: PostboxDecoder) { + self.gift = decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeObject(self.gift, forKey: "gift") + } +} + public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable { public let url: String public let displayUrl: String diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/MediaArea.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/MediaArea.swift index dec49da75a..eb6ebed9d1 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/MediaArea.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/MediaArea.swift @@ -152,6 +152,7 @@ public enum MediaArea: Codable, Equatable { case channelMessage(coordinates: Coordinates, messageId: EngineMessage.Id) case link(coordinates: Coordinates, url: String) case weather(coordinates: Coordinates, emoji: String, temperature: Double, color: Int32) + case starGift(coordinates: Coordinates, slug: String) public struct ReactionFlags: OptionSet { public var rawValue: Int32 @@ -174,6 +175,7 @@ public enum MediaArea: Codable, Equatable { case channelMessage case link case weather + case starGift } public enum DecodingError: Error { @@ -210,6 +212,10 @@ public enum MediaArea: Codable, Equatable { let temperature = try container.decode(Double.self, forKey: .temperature) let color = try container.decodeIfPresent(Int32.self, forKey: .color) ?? 0 self = .weather(coordinates: coordinates, emoji: emoji, temperature: temperature, color: color) + case .starGift: + let coordinates = try container.decode(MediaArea.Coordinates.self, forKey: .coordinates) + let slug = try container.decode(String.self, forKey: .value) + self = .starGift(coordinates: coordinates, slug: slug) } } @@ -240,6 +246,10 @@ public enum MediaArea: Codable, Equatable { try container.encode(emoji, forKey: .value) try container.encode(temperature, forKey: .temperature) try container.encode(color, forKey: .color) + case let .starGift(coordinates, slug): + try container.encode(MediaAreaType.starGift.rawValue, forKey: .type) + try container.encode(coordinates, forKey: .coordinates) + try container.encode(slug, forKey: .value) } } } @@ -257,6 +267,8 @@ public extension MediaArea { return coordinates case let .weather(coordinates, _, _, _): return coordinates + case let .starGift(coordinates, _): + return coordinates } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift index 1ddc78009f..bfeb70e88f 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift @@ -204,7 +204,9 @@ public enum StarGift: Equatable, Codable, PostboxCoding { case id case title case number + case slug case ownerPeerId + case ownerName case attributes case availability } @@ -428,6 +430,11 @@ public enum StarGift: Equatable, Codable, PostboxCoding { encoder.encodeInt32(self.total, forKey: CodingKeys.total.rawValue) } } + + public enum Owner: Equatable { + case peerId(EnginePeer.Id) + case name(String) + } public enum DecodingError: Error { case generic @@ -436,15 +443,17 @@ public enum StarGift: Equatable, Codable, PostboxCoding { public let id: Int64 public let title: String public let number: Int32 - public let ownerPeerId: EnginePeer.Id + public let slug: String + public let owner: Owner public let attributes: [Attribute] public let availability: Availability - public init(id: Int64, title: String, number: Int32, ownerPeerId: EnginePeer.Id, attributes: [Attribute], availability: Availability) { + public init(id: Int64, title: String, number: Int32, slug: String, owner: Owner, attributes: [Attribute], availability: Availability) { self.id = id self.title = title self.number = number - self.ownerPeerId = ownerPeerId + self.slug = slug + self.owner = owner self.attributes = attributes self.availability = availability } @@ -454,7 +463,14 @@ public enum StarGift: Equatable, Codable, PostboxCoding { self.id = try container.decode(Int64.self, forKey: .id) self.title = try container.decode(String.self, forKey: .title) self.number = try container.decode(Int32.self, forKey: .number) - self.ownerPeerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(try container.decode(Int64.self, forKey: .ownerPeerId))) + self.slug = try container.decodeIfPresent(String.self, forKey: .slug) ?? "" + if let ownerId = try container.decodeIfPresent(Int64.self, forKey: .ownerPeerId) { + self.owner = .peerId(EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(ownerId))) + } else if let ownerName = try container.decodeIfPresent(String.self, forKey: .ownerName) { + self.owner = .name(ownerName) + } else { + self.owner = .name("Unknown") + } self.attributes = try container.decode([UniqueGift.Attribute].self, forKey: .attributes) self.availability = try container.decode(UniqueGift.Availability.self, forKey: .availability) } @@ -463,7 +479,14 @@ public enum StarGift: Equatable, Codable, PostboxCoding { self.id = decoder.decodeInt64ForKey(CodingKeys.id.rawValue, orElse: 0) self.title = decoder.decodeStringForKey(CodingKeys.title.rawValue, orElse: "") self.number = decoder.decodeInt32ForKey(CodingKeys.number.rawValue, orElse: 0) - self.ownerPeerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(decoder.decodeInt64ForKey(CodingKeys.ownerPeerId.rawValue, orElse: 0))) + self.slug = decoder.decodeStringForKey(CodingKeys.slug.rawValue, orElse: "") + if let ownerId = decoder.decodeOptionalInt64ForKey(CodingKeys.ownerPeerId.rawValue) { + self.owner = .peerId(EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(ownerId))) + } else if let ownerName = decoder.decodeOptionalStringForKey(CodingKeys.ownerName.rawValue) { + self.owner = .name(ownerName) + } else { + self.owner = .name("Unknown") + } self.attributes = (try? decoder.decodeObjectArrayWithCustomDecoderForKey(CodingKeys.attributes.rawValue, decoder: { UniqueGift.Attribute(decoder: $0) })) ?? [] self.availability = decoder.decodeObjectForKey(CodingKeys.availability.rawValue, decoder: { UniqueGift.Availability(decoder: $0) }) as! UniqueGift.Availability } @@ -473,7 +496,13 @@ public enum StarGift: Equatable, Codable, PostboxCoding { try container.encode(self.id, forKey: .id) try container.encode(self.title, forKey: .title) try container.encode(self.number, forKey: .number) - try container.encode(self.ownerPeerId.id._internalGetInt64Value(), forKey: .ownerPeerId) + try container.encode(self.slug, forKey: .slug) + switch self.owner { + case let .peerId(peerId): + try container.encode(peerId.id._internalGetInt64Value(), forKey: .ownerPeerId) + case let .name(name): + try container.encode(name, forKey: .ownerName) + } try container.encode(self.attributes, forKey: .attributes) try container.encode(self.availability, forKey: .availability) } @@ -482,7 +511,13 @@ public enum StarGift: Equatable, Codable, PostboxCoding { encoder.encodeInt64(self.id, forKey: CodingKeys.id.rawValue) encoder.encodeString(self.title, forKey: CodingKeys.title.rawValue) encoder.encodeInt32(self.number, forKey: CodingKeys.number.rawValue) - encoder.encodeInt64(self.ownerPeerId.id._internalGetInt64Value(), forKey: CodingKeys.ownerPeerId.rawValue) + encoder.encodeString(self.slug, forKey: CodingKeys.slug.rawValue) + switch self.owner { + case let .peerId(peerId): + encoder.encodeInt64(peerId.id._internalGetInt64Value(), forKey: CodingKeys.ownerPeerId.rawValue) + case let .name(name): + encoder.encodeString(name, forKey: CodingKeys.ownerName.rawValue) + } encoder.encodeObjectArray(self.attributes, forKey: CodingKeys.attributes.rawValue) encoder.encodeObject(self.availability, forKey: CodingKeys.availability.rawValue) } @@ -570,8 +605,16 @@ extension StarGift { return nil } self = .generic(StarGift.Gift(id: id, file: file, price: stars, convertStars: convertStars, availability: availability, soldOut: soldOut, flags: flags, upgradeStars: upgradeStars)) - case let .starGiftUnique(id, title, num, ownerId, attributes, availabilityIssued, availabilityTotal): - self = .unique(StarGift.UniqueGift(id: id, title: title, number: num, ownerPeerId: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(ownerId)), attributes: attributes.compactMap { UniqueGift.Attribute(apiAttribute: $0) }, availability: UniqueGift.Availability(issued: availabilityIssued, total: availabilityTotal))) + case let .starGiftUnique(_, id, title, slug, num, ownerId, ownerName, attributes, availabilityIssued, availabilityTotal): + let owner: StarGift.UniqueGift.Owner + if let ownerId { + owner = .peerId(EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(ownerId))) + } else if let ownerName { + owner = .name(ownerName) + } else { + return nil + } + self = .unique(StarGift.UniqueGift(id: id, title: title, number: num, slug: slug, owner: owner, attributes: attributes.compactMap { UniqueGift.Attribute(apiAttribute: $0) }, availability: UniqueGift.Availability(issued: availabilityIssued, total: availabilityTotal))) } } } @@ -1311,3 +1354,29 @@ extension StarGift.UniqueGift.Attribute { } } } + + +func _internal_getUniqueStarGift(account: Account, slug: String) -> Signal { + return account.network.request(Api.functions.payments.getUniqueStarGift(slug: slug)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + if let result = result { + switch result { + case let .uniqueStarGift(gift, users): + return account.postbox.transaction { transaction in + let parsedPeers = AccumulatedPeers(users: users) + updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers) + guard case let .unique(uniqueGift) = StarGift(apiStarGift: gift) else { + return nil + } + return uniqueGift + } + } + } else { + return .single(nil) + } + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift index 283a831b99..6ab372a3d2 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift @@ -132,5 +132,9 @@ public extension TelegramEngine { public func starGiftUpgradePreview(giftId: Int64) -> Signal<[StarGift.UniqueGift.Attribute], NoError> { return _internal_starGiftUpgradePreview(account: self.account, giftId: giftId) } + + public func getUniqueStarGift(slug: String) -> Signal { + return _internal_getUniqueStarGift(account: self.account, slug: slug) + } } } diff --git a/submodules/TelegramCore/Sources/WebpagePreview.swift b/submodules/TelegramCore/Sources/WebpagePreview.swift index d1c6064370..1fa282410b 100644 --- a/submodules/TelegramCore/Sources/WebpagePreview.swift +++ b/submodules/TelegramCore/Sources/WebpagePreview.swift @@ -155,8 +155,8 @@ public func webpagePreviewWithProgress(account: Account, urls: [String], webpage } return account.network.requestWithAdditionalInfo(Api.functions.messages.getWebPagePreview(flags: 0, message: urls.joined(separator: " "), entities: nil), info: .progress) - |> `catch` { _ -> Signal, NoError> in - return .single(.result(.messageMediaEmpty)) + |> `catch` { _ -> Signal, NoError> in + return .single(.result(.webPagePreview(media: .messageMediaEmpty, users: []))) } |> mapToSignal { result -> Signal in switch result { @@ -169,36 +169,44 @@ public func webpagePreviewWithProgress(account: Account, urls: [String], webpage return .complete() } case let .result(result): - if let preCachedResources = result.preCachedResources { + if case let .webPagePreview(result, _) = result, let preCachedResources = result.preCachedResources { for (resource, data) in preCachedResources { account.postbox.mediaBox.storeResourceData(resource.id, data: data) } } switch result { - case let .messageMediaWebPage(flags, webpage): - let _ = flags - if let media = telegramMediaWebpageFromApiWebpage(webpage), let url = media.content.url { - if case .Loaded = media.content { - return .single(.result(WebpagePreviewResult.Result(webpage: media, sourceUrl: url))) - } else { - return .single(.result(WebpagePreviewResult.Result(webpage: media, sourceUrl: url))) - |> then( - account.stateManager.updatedWebpage(media.webpageId) - |> take(1) - |> map { next -> WebpagePreviewWithProgressResult in - if let url = next.content.url { - return .result(WebpagePreviewResult.Result(webpage: next, sourceUrl: url)) - } else { - return .result(nil) - } + case let .webPagePreview(media, users): + switch media { + case let .messageMediaWebPage(_, webpage): + return account.postbox.transaction { transaction -> Signal in + let peers = AccumulatedPeers(users: users) + updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: peers) + + if let media = telegramMediaWebpageFromApiWebpage(webpage), let url = media.content.url { + if case .Loaded = media.content { + return .single(.result(WebpagePreviewResult.Result(webpage: media, sourceUrl: url))) + } else { + return .single(.result(WebpagePreviewResult.Result(webpage: media, sourceUrl: url))) + |> then( + account.stateManager.updatedWebpage(media.webpageId) + |> take(1) + |> map { next -> WebpagePreviewWithProgressResult in + if let url = next.content.url { + return .result(WebpagePreviewResult.Result(webpage: next, sourceUrl: url)) + } else { + return .result(nil) + } + } + ) } - ) + } else { + return .single(.result(nil)) + } } - } else { + |> switchToLatest + default: return .single(.result(nil)) } - default: - return .single(.result(nil)) } } } diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraCodeResultComponent.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraCodeResultComponent.swift index a3b2b29885..11f953f24d 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraCodeResultComponent.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraCodeResultComponent.swift @@ -189,8 +189,7 @@ final class CameraCodeResultComponent: Component { view.frame = CGRect(origin: CGPoint(x: 54.0, y: 9.0), size: titleSize) } - //TODO:localize - let subtitleString = NSMutableAttributedString(string: "Open Chat >", font: Font.regular(15.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.7)) + let subtitleString = NSMutableAttributedString(string: "\(presentationData.strings.Camera_OpenChat) >", font: Font.regular(15.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.7)) if let range = subtitleString.string.range(of: ">"), let arrowImage = UIImage(bundleImageName: "Item List/InlineTextRightArrow") { subtitleString.addAttribute(.attachment, value: arrowImage, range: NSRange(range, in: subtitleString.string)) subtitleString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: subtitleString.string)) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift index c5559abc99..fe382630ff 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift @@ -366,7 +366,6 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { var buttonTitle = item.presentationData.strings.Notification_PremiumGift_View var buttonIcon: String? var ribbonTitle = "" - var hasServiceMessage = true var textSpacing: CGFloat = 0.0 var isStarGift = false @@ -381,6 +380,9 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { var uniquePatternColor: UIColor? var uniquePatternFile: TelegramMediaFile? + let isStoryEntity = item.message.id.id == -1 + var hasServiceMessage = !isStoryEntity + for media in item.message.media { if let action = media as? TelegramMediaAction { switch action.action { @@ -560,10 +562,10 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } else { authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? "" } - title = item.presentationData.strings.Notification_StarGift_Title(authorName).string - text = "**\(uniqueGift.title) #\(uniqueGift.number)**" - ribbonTitle = item.presentationData.strings.Notification_StarGift_Gift - buttonTitle = item.presentationData.strings.Notification_StarGift_View + title = isStoryEntity ? uniqueGift.title : item.presentationData.strings.Notification_StarGift_Title(authorName).string + text = isStoryEntity ? "**Collectible #\(uniqueGift.number)**" : "**\(uniqueGift.title) #\(uniqueGift.number)**" + ribbonTitle = isStoryEntity ? "" : item.presentationData.strings.Notification_StarGift_Gift + buttonTitle = isStoryEntity ? "" : item.presentationData.strings.Notification_StarGift_View modelTitle = item.presentationData.strings.Notification_StarGift_Model backdropTitle = item.presentationData.strings.Notification_StarGift_Backdrop symbolTitle = item.presentationData.strings.Notification_StarGift_Symbol @@ -697,6 +699,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { if !buttonTitle.isEmpty { giftSize.height += 48.0 + } else if isStoryEntity { + giftSize.height += 12.0 } var labelRects = labelLayout.linesRects() @@ -754,7 +758,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } } - let overlayColor = item.presentationData.theme.theme.overallDarkAppearance ? UIColor(rgb: 0xffffff, alpha: 0.12) : UIColor(rgb: 0x000000, alpha: 0.12) + let overlayColor = item.presentationData.theme.theme.overallDarkAppearance && uniquePatternFile == nil ? UIColor(rgb: 0xffffff, alpha: 0.12) : UIColor(rgb: 0x000000, alpha: 0.12) let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - giftSize.width) / 2.0), y: hasServiceMessage ? labelLayout.size.height + 12.0 : 0.0), size: giftSize) let mediaBackgroundFrame = imageFrame.insetBy(dx: -2.0, dy: -2.0) @@ -767,11 +771,12 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } let animationFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - iconSize.width) / 2.0), y: mediaBackgroundFrame.minY - 16.0 + iconOffset), size: iconSize) strongSelf.animationNode.frame = animationFrame + strongSelf.animationNode.isHidden = isStoryEntity strongSelf.buttonNode.isHidden = buttonTitle.isEmpty strongSelf.buttonTitleNode.isHidden = buttonTitle.isEmpty - if strongSelf.item == nil { + if strongSelf.item == nil && !isStoryEntity { strongSelf.animationNode.started = { [weak self] in if let strongSelf = self { let current = CACurrentMediaTime() @@ -883,63 +888,78 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.dustNode = nil } - var middleX = mediaBackgroundFrame.width / 2.0 - if let (modelValueLayout, _) = modelValueLayoutAndApply, let (backdropValueLayout, _) = backdropValueLayoutAndApply, let (symbolValueLayout, _) = symbolValueLayoutAndApply { - let maxWidth = max(modelValueLayout.size.width, max(backdropValueLayout.size.width, symbolValueLayout.size.width)) - middleX = min(mediaBackgroundFrame.width - maxWidth - 16.0, middleX) + let attributeSpacing: CGFloat = 6.0 + let attributeVerticalSpacing: CGFloat = 22.0 + var attributeMidpoints: [CGFloat] = [] + + func appendAttributeMidpoint(titleLayout: TextNodeLayout?, valueLayout: TextNodeLayout?) { + if let titleLayout, let valueLayout { + let totalWidth = titleLayout.size.width + attributeSpacing + valueLayout.size.width + let titleOffset = titleLayout.size.width + attributeSpacing / 2.0 + let midpoint = (mediaBackgroundFrame.width - totalWidth) / 2.0 + titleOffset + attributeMidpoints.append(midpoint) + } } - - let titleMaxX: CGFloat = mediaBackgroundFrame.minX + middleX - 2.0 - let valueMinX: CGFloat = mediaBackgroundFrame.minX + middleX + 3.0 + appendAttributeMidpoint(titleLayout: modelTitleLayoutAndApply?.0, valueLayout: modelValueLayoutAndApply?.0) + appendAttributeMidpoint(titleLayout: backdropTitleLayoutAndApply?.0, valueLayout: backdropValueLayoutAndApply?.0) + appendAttributeMidpoint(titleLayout: symbolTitleLayoutAndApply?.0, valueLayout: symbolValueLayoutAndApply?.0) + + let middleX = attributeMidpoints.isEmpty ? mediaBackgroundFrame.width / 2.0 : attributeMidpoints.reduce(0, +) / CGFloat(attributeMidpoints.count) + + let titleMaxX: CGFloat = mediaBackgroundFrame.minX + middleX - attributeSpacing / 2.0 + let valueMinX: CGFloat = mediaBackgroundFrame.minX + middleX + attributeSpacing / 2.0 - if let (modelTitleLayout, modelTitleApply) = modelTitleLayoutAndApply { - if strongSelf.modelTitleTextNode.supernode == nil { - strongSelf.addSubnode(strongSelf.modelTitleTextNode) + func positionAttributeNodes( + titleTextNode: TextNode, + valueTextNode: TextNode, + titleLayoutAndApply: (TextNodeLayout, () -> TextNode)?, + valueLayoutAndApply: (TextNodeLayout, () -> TextNode)?, + yOffset: CGFloat + ) { + if let (titleLayout, titleApply) = titleLayoutAndApply { + if titleTextNode.supernode == nil { + strongSelf.addSubnode(titleTextNode) + } + let _ = titleApply() + titleTextNode.frame = CGRect( + origin: CGPoint(x: titleMaxX - titleLayout.size.width, y: clippingTextFrame.maxY + yOffset), + size: titleLayout.size + ) + } + if let (valueLayout, valueApply) = valueLayoutAndApply { + if valueTextNode.supernode == nil { + strongSelf.addSubnode(valueTextNode) + } + let _ = valueApply() + valueTextNode.frame = CGRect( + origin: CGPoint(x: valueMinX, y: clippingTextFrame.maxY + yOffset), + size: valueLayout.size + ) } - let _ = modelTitleApply() - strongSelf.modelTitleTextNode.frame = CGRect(origin: CGPoint(x: titleMaxX - modelTitleLayout.size.width, y: clippingTextFrame.maxY + 10.0), size: modelTitleLayout.size) - } - - if let (modelValueLayout, modelValueApply) = modelValueLayoutAndApply { - if strongSelf.modelValueTextNode.supernode == nil { - strongSelf.addSubnode(strongSelf.modelValueTextNode) - } - let _ = modelValueApply() - strongSelf.modelValueTextNode.frame = CGRect(origin: CGPoint(x: valueMinX, y: clippingTextFrame.maxY + 10.0), size: modelValueLayout.size) - } - - if let (backdropTitleLayout, backdropTitleApply) = backdropTitleLayoutAndApply { - if strongSelf.backdropTitleTextNode.supernode == nil { - strongSelf.addSubnode(strongSelf.backdropTitleTextNode) - } - let _ = backdropTitleApply() - strongSelf.backdropTitleTextNode.frame = CGRect(origin: CGPoint(x: titleMaxX - backdropTitleLayout.size.width, y: clippingTextFrame.maxY + 32.0), size: backdropTitleLayout.size) - } - - if let (backdropValueLayout, backdropValueApply) = backdropValueLayoutAndApply { - if strongSelf.backdropValueTextNode.supernode == nil { - strongSelf.addSubnode(strongSelf.backdropValueTextNode) - } - let _ = backdropValueApply() - strongSelf.backdropValueTextNode.frame = CGRect(origin: CGPoint(x: valueMinX, y: clippingTextFrame.maxY + 32.0), size: backdropValueLayout.size) - } - - if let (symbolTitleLayout, symbolTitleApply) = symbolTitleLayoutAndApply { - if strongSelf.symbolTitleTextNode.supernode == nil { - strongSelf.addSubnode(strongSelf.symbolTitleTextNode) - } - let _ = symbolTitleApply() - strongSelf.symbolTitleTextNode.frame = CGRect(origin: CGPoint(x: titleMaxX - symbolTitleLayout.size.width, y: clippingTextFrame.maxY + 54.0), size: symbolTitleLayout.size) - } - - if let (symbolValueLayout, symbolValueApply) = symbolValueLayoutAndApply { - if strongSelf.symbolValueTextNode.supernode == nil { - strongSelf.addSubnode(strongSelf.symbolValueTextNode) - } - let _ = symbolValueApply() - strongSelf.symbolValueTextNode.frame = CGRect(origin: CGPoint(x: valueMinX, y: clippingTextFrame.maxY + 54.0), size: symbolValueLayout.size) } + positionAttributeNodes( + titleTextNode: strongSelf.modelTitleTextNode, + valueTextNode: strongSelf.modelValueTextNode, + titleLayoutAndApply: modelTitleLayoutAndApply, + valueLayoutAndApply: modelValueLayoutAndApply, + yOffset: 10.0 + ) + positionAttributeNodes( + titleTextNode: strongSelf.backdropTitleTextNode, + valueTextNode: strongSelf.backdropValueTextNode, + titleLayoutAndApply: backdropTitleLayoutAndApply, + valueLayoutAndApply: backdropValueLayoutAndApply, + yOffset: 10.0 + attributeVerticalSpacing + ) + positionAttributeNodes( + titleTextNode: strongSelf.symbolTitleTextNode, + valueTextNode: strongSelf.symbolValueTextNode, + titleLayoutAndApply: symbolTitleLayoutAndApply, + valueLayoutAndApply: symbolValueLayoutAndApply, + yOffset: 10.0 + attributeVerticalSpacing * 2 + ) + var buttonSize = CGSize(width: buttonTitleLayout.size.width + 38.0, height: 34.0) var buttonOriginY = clippingTextFrame.maxY + 10.0 if modelTitleLayoutAndApply != nil { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift index ba57a1641a..d87fe99d94 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -457,6 +457,8 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent } } actionTitle = isEmoji ? item.presentationData.strings.Conversation_ViewEmojis : item.presentationData.strings.Conversation_ViewStickers + case "telegram_nft": + actionTitle = item.presentationData.strings.Conversation_ViewStarGift default: break } diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift index a87dbf9b27..ba5c035ffe 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift @@ -1381,6 +1381,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { break case .premiumMultiGift: break + case .collectible: + break case .messageLink: break } diff --git a/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift b/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift index 62e8261f9c..a2ac2dd942 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift @@ -431,10 +431,9 @@ public final class GiftItemComponent: Component { } price = priceValue case .uniqueGift: - //TODO:localize buttonColor = UIColor.white starsColor = UIColor.white - price = "Unique" + price = "" } let buttonSize = self.button.update( diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD index 942da7286a..6b06e7e5d7 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD @@ -42,6 +42,7 @@ swift_library( "//submodules/ConfettiEffect", "//submodules/TooltipUI", "//submodules/TelegramUI/Components/Gifts/GiftItemComponent", + "//submodules/MoreButtonNode", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/ButtonsComponent.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/ButtonsComponent.swift new file mode 100644 index 0000000000..09e7712a60 --- /dev/null +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/ButtonsComponent.swift @@ -0,0 +1,143 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import ComponentFlow +import BundleIconComponent +import MultilineTextComponent +import MoreButtonNode +import AccountContext +import TelegramPresentationData + +final class ButtonsComponent: Component { + let theme: PresentationTheme + let isOverlay: Bool + let showMoreButton: Bool + let closePressed: () -> Void + let morePressed: (ASDisplayNode, ContextGesture?) -> Void + + init( + theme: PresentationTheme, + isOverlay: Bool, + showMoreButton: Bool, + closePressed: @escaping () -> Void, + morePressed: @escaping (ASDisplayNode, ContextGesture?) -> Void + ) { + self.theme = theme + self.isOverlay = isOverlay + self.showMoreButton = showMoreButton + self.closePressed = closePressed + self.morePressed = morePressed + } + + static func ==(lhs: ButtonsComponent, rhs: ButtonsComponent) -> Bool { + return lhs.theme === rhs.theme && lhs.isOverlay == rhs.isOverlay && lhs.showMoreButton == rhs.showMoreButton + } + + final class View: UIView { + private let backgroundView = UIView() + + private let closeButton = HighlightTrackingButton() + private let closeIcon = UIImageView() + private let moreNode = MoreButtonNode(theme: defaultPresentationTheme, size: CGSize(width: 36.0, height: 36.0), encircled: false) + + private var component: ButtonsComponent? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + super.init(frame: frame) + + self.backgroundView.clipsToBounds = true + self.addSubview(self.backgroundView) + + self.closeIcon.image = generateCloseButtonImage() + self.moreNode.updateColor(.white, transition: .immediate) + + self.backgroundView.addSubview(self.moreNode.view) + self.backgroundView.addSubview(self.closeButton) + self.backgroundView.addSubview(self.closeIcon) + + self.closeButton.highligthedChanged = { [weak self] highlighted in + guard let self else { + return + } + if highlighted { + self.closeIcon.layer.removeAnimation(forKey: "opacity") + self.closeIcon.alpha = 0.6 + } else { + self.closeIcon.alpha = 1.0 + self.closeIcon.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2) + } + } + self.closeButton.addTarget(self, action: #selector(self.closePressed), for: .touchUpInside) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func closePressed() { + guard let component = self.component else { + return + } + component.closePressed() + } + + func update(component: ButtonsComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + let backgroundSize = CGSize(width: component.showMoreButton ? 70.0 : 30.0, height: 30.0) + self.backgroundView.layer.cornerRadius = backgroundSize.height / 2.0 + + let backgroundColor: UIColor = component.isOverlay ? UIColor(rgb: 0xffffff, alpha: 0.1) : UIColor(rgb: 0x808084, alpha: 0.1) + let foregroundColor: UIColor = component.isOverlay ? .white : component.theme.actionSheet.inputClearButtonColor + transition.setBackgroundColor(view: self.backgroundView, color: backgroundColor) + transition.setTintColor(view: self.closeIcon, color: foregroundColor) + + let backgroundFrame = CGRect(origin: .zero, size: backgroundSize) + transition.setFrame(view: self.backgroundView, frame: backgroundFrame) + + transition.setFrame(view: self.moreNode.view, frame: CGRect(origin: CGPoint(x: -7.0, y: -4.0), size: CGSize(width: 36.0, height: 36.0))) + transition.setAlpha(view: self.moreNode.view, alpha: component.showMoreButton ? 1.0 : 0.0) + self.moreNode.action = { [weak self] node, gesture in + guard let self, let component = self.component else { + return + } + component.morePressed(node, gesture) + } + + let closeFrame = CGRect(origin: CGPoint(x: backgroundSize.width - 30.0 - (component.showMoreButton ? 3.0 : 0.0), y: 0.0), size: CGSize(width: 30.0, height: 30.0)) + transition.setFrame(view: self.closeIcon, frame: closeFrame) + transition.setFrame(view: self.closeButton, frame: closeFrame) + + return backgroundSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private func generateCloseButtonImage() -> UIImage? { + return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.setLineWidth(2.0) + context.setLineCap(.round) + context.setStrokeColor(UIColor.white.cgColor) + + context.move(to: CGPoint(x: 10.0, y: 10.0)) + context.addLine(to: CGPoint(x: 20.0, y: 20.0)) + context.strokePath() + + context.move(to: CGPoint(x: 20.0, y: 10.0)) + context.addLine(to: CGPoint(x: 10.0, y: 20.0)) + context.strokePath() + })?.withRenderingMode(.alwaysTemplate) +} diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index 87b8e7ce7c..b6fb2a1f74 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -29,6 +29,7 @@ import CheckComponent import TooltipUI import GiftAnimationComponent import LottieComponent +import ContextUI private let modelButtonTag = GenericComponentViewTag() private let backdropButtonTag = GenericComponentViewTag() @@ -50,6 +51,7 @@ private final class GiftViewSheetContent: CombinedComponent { let upgradeGift: ((Int64?, Bool) -> Signal) let showAttributeInfo: (Any, Float) -> Void let viewUpgraded: (EngineMessage.Id) -> Void + let openMore: (ASDisplayNode, ContextGesture?) -> Void let getController: () -> ViewController? init( @@ -66,6 +68,7 @@ private final class GiftViewSheetContent: CombinedComponent { upgradeGift: @escaping ((Int64?, Bool) -> Signal), showAttributeInfo: @escaping (Any, Float) -> Void, viewUpgraded: @escaping (EngineMessage.Id) -> Void, + openMore: @escaping (ASDisplayNode, ContextGesture?) -> Void, getController: @escaping () -> ViewController? ) { self.context = context @@ -81,6 +84,7 @@ private final class GiftViewSheetContent: CombinedComponent { self.upgradeGift = upgradeGift self.showAttributeInfo = showAttributeInfo self.viewUpgraded = viewUpgraded + self.openMore = openMore self.getController = getController } @@ -108,8 +112,6 @@ private final class GiftViewSheetContent: CombinedComponent { var cachedCircleImage: UIImage? var cachedStarImage: (UIImage, PresentationTheme)? - var cachedCloseImage: (UIImage, PresentationTheme)? - var cachedOverlayCloseImage: UIImage? var cachedChevronImage: (UIImage, PresentationTheme)? var cachedSmallChevronImage: (UIImage, PresentationTheme)? @@ -158,12 +160,17 @@ private final class GiftViewSheetContent: CombinedComponent { self.keepOriginalInfo = true } - var peerIds: [EnginePeer.Id] = [arguments.peerId, context.account.peerId] + var peerIds: [EnginePeer.Id] = [context.account.peerId] + if let peerId = arguments.peerId { + peerIds.append(peerId) + } if let fromPeerId = arguments.fromPeerId, !peerIds.contains(fromPeerId) { peerIds.append(fromPeerId) } if case let .unique(gift) = arguments.gift { - peerIds.append(gift.ownerPeerId) + if case let .peerId(peerId) = gift.owner { + peerIds.append(peerId) + } for attribute in gift.attributes { if case let .originalInfo(senderPeerId, recipientPeerId, _, _, _) = attribute { if let senderPeerId { @@ -272,10 +279,10 @@ private final class GiftViewSheetContent: CombinedComponent { } func commitUpgrade() { - guard let arguments = self.subject.arguments, let starsContext = self.context.starsContext, let starsState = starsContext.currentState else { + guard let arguments = self.subject.arguments, let peerId = arguments.peerId, let starsContext = self.context.starsContext, let starsState = starsContext.currentState else { return } - let peerId = arguments.peerId + let proceed: (Int64?) -> Void = { formId in self.inProgress = true self.updated() @@ -332,7 +339,7 @@ private final class GiftViewSheetContent: CombinedComponent { } static var body: Body { - let closeButton = Child(Button.self) + let buttons = Child(ButtonsComponent.self) let animation = Child(GiftCompositionComponent.self) let title = Child(MultilineTextComponent.self) let description = Child(MultilineTextComponent.self) @@ -436,22 +443,7 @@ private final class GiftViewSheetContent: CombinedComponent { convertStars = nil titleString = "" } - - let closeImage: UIImage - let closeOverlayImage: UIImage - if let (image, theme) = state.cachedCloseImage, theme === environment.theme { - closeImage = image - } else { - closeImage = generateCloseButtonImage(backgroundColor: UIColor(rgb: 0x808084, alpha: 0.1), foregroundColor: theme.actionSheet.inputClearButtonColor)! - state.cachedCloseImage = (closeImage, theme) - } - if let image = state.cachedOverlayCloseImage { - closeOverlayImage = image - } else { - closeOverlayImage = generateCloseButtonImage(backgroundColor: UIColor(rgb: 0xffffff, alpha: 0.1), foregroundColor: .white)! - state.cachedOverlayCloseImage = closeOverlayImage - } - + var showUpgradePreview = false if state.inUpgradePreview, let _ = state.sampleGiftAttributes { showUpgradePreview = true @@ -460,10 +452,12 @@ private final class GiftViewSheetContent: CombinedComponent { } let cancel = component.cancel - let closeButton = closeButton.update( - component: Button( - content: AnyComponent(Image(image: showUpgradePreview || uniqueGift != nil ? closeOverlayImage : closeImage)), - action: { [weak state] in + let buttons = buttons.update( + component: ButtonsComponent( + theme: theme, + isOverlay: showUpgradePreview || uniqueGift != nil, + showMoreButton: uniqueGift != nil, + closePressed: { [weak state] in guard let state else { return } @@ -473,10 +467,13 @@ private final class GiftViewSheetContent: CombinedComponent { } else { cancel(true) } + }, + morePressed: { node, gesture in + component.openMore(node, gesture) } ), availableSize: CGSize(width: 30.0, height: 30.0), - transition: .immediate + transition: context.transition ) var originY: CGFloat = 0.0 @@ -888,70 +885,81 @@ private final class GiftViewSheetContent: CombinedComponent { if !soldOut { if let uniqueGift { - if let peer = state.peerMap[uniqueGift.ownerPeerId] { - let ownerComponent: AnyComponent - if let _ = subject.arguments?.transferStars { - ownerComponent = AnyComponent( - HStack([ - AnyComponentWithIdentity( - id: AnyHashable(0), - component: AnyComponent(Button( - content: AnyComponent( - PeerCellComponent( + switch uniqueGift.owner { + case let .peerId(peerId): + if let peer = state.peerMap[peerId] { + let ownerComponent: AnyComponent + if let _ = subject.arguments?.transferStars { + ownerComponent = AnyComponent( + HStack([ + AnyComponentWithIdentity( + id: AnyHashable(0), + component: AnyComponent(Button( + content: AnyComponent( + PeerCellComponent( + context: component.context, + theme: theme, + strings: strings, + peer: peer + ) + ), + action: { + component.openPeer(peer) + Queue.mainQueue().after(1.0, { + component.cancel(false) + }) + } + )) + ), + AnyComponentWithIdentity( + id: AnyHashable(1), + component: AnyComponent(Button( + content: AnyComponent(ButtonContentComponent( context: component.context, - theme: theme, - strings: strings, - peer: peer - ) - ), - action: { - component.openPeer(peer) - Queue.mainQueue().after(1.0, { - component.cancel(false) - }) - } - )) + text: strings.Gift_Unique_Transfer, + color: theme.list.itemAccentColor + )), + action: { + component.transferGift() + Queue.mainQueue().after(1.0, { + component.cancel(false) + }) + } + )) + ) + ], spacing: 4.0) + ) + } else { + ownerComponent = AnyComponent(Button( + content: AnyComponent( + PeerCellComponent( + context: component.context, + theme: theme, + strings: strings, + peer: peer + ) ), - AnyComponentWithIdentity( - id: AnyHashable(1), - component: AnyComponent(Button( - content: AnyComponent(ButtonContentComponent( - context: component.context, - text: strings.Gift_Unique_Transfer, - color: theme.list.itemAccentColor - )), - action: { - component.transferGift() - Queue.mainQueue().after(1.0, { - component.cancel(false) - }) - } - )) - ) - ], spacing: 4.0) - ) - } else { - ownerComponent = AnyComponent(Button( - content: AnyComponent( - PeerCellComponent( - context: component.context, - theme: theme, - strings: strings, - peer: peer - ) - ), - action: { - component.openPeer(peer) - Queue.mainQueue().after(1.0, { - component.cancel(false) - }) - } + action: { + component.openPeer(peer) + Queue.mainQueue().after(1.0, { + component.cancel(false) + }) + } + )) + } + tableItems.append(.init( + id: "owner", + title: strings.Gift_Unique_Owner, + component: ownerComponent )) } + case let .name(name): tableItems.append(.init( - id: "owner", + id: "anon_owner", title: strings.Gift_Unique_Owner, - component: ownerComponent + component: AnyComponent( + MultilineTextComponent(text: .plain(NSAttributedString(string: name, font: tableFont, textColor: tableTextColor))) + ) )) } } else if let peerId = subject.arguments?.fromPeerId, let peer = state.peerMap[peerId] { @@ -1593,8 +1601,8 @@ private final class GiftViewSheetContent: CombinedComponent { originY += buttonChild.size.height originY += 7.0 - context.add(closeButton - .position(CGPoint(x: context.availableSize.width - environment.safeInsets.left - closeButton.size.width, y: 28.0)) + context.add(buttons + .position(CGPoint(x: context.availableSize.width - environment.safeInsets.left - 16.0 - buttons.size.width / 2.0, y: 28.0)) ) let contentSize = CGSize(width: context.availableSize.width, height: originY + 5.0 + environment.safeInsets.bottom) @@ -1618,6 +1626,7 @@ private final class GiftViewSheetComponent: CombinedComponent { let transferGift: () -> Void let upgradeGift: ((Int64?, Bool) -> Signal) let viewUpgraded: (EngineMessage.Id) -> Void + let openMore: (ASDisplayNode, ContextGesture?) -> Void let showAttributeInfo: (Any, Float) -> Void init( @@ -1632,6 +1641,7 @@ private final class GiftViewSheetComponent: CombinedComponent { transferGift: @escaping () -> Void, upgradeGift: @escaping ((Int64?, Bool) -> Signal), viewUpgraded: @escaping (EngineMessage.Id) -> Void, + openMore: @escaping (ASDisplayNode, ContextGesture?) -> Void, showAttributeInfo: @escaping (Any, Float) -> Void ) { self.context = context @@ -1645,6 +1655,7 @@ private final class GiftViewSheetComponent: CombinedComponent { self.transferGift = transferGift self.upgradeGift = upgradeGift self.viewUpgraded = viewUpgraded + self.openMore = openMore self.showAttributeInfo = showAttributeInfo } @@ -1695,6 +1706,7 @@ private final class GiftViewSheetComponent: CombinedComponent { upgradeGift: context.component.upgradeGift, showAttributeInfo: context.component.showAttributeInfo, viewUpgraded: context.component.viewUpgraded, + openMore: context.component.openMore, getController: controller )), backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor), @@ -1764,11 +1776,12 @@ private final class GiftViewSheetComponent: CombinedComponent { public class GiftViewScreen: ViewControllerComponentContainer { public enum Subject: Equatable { case message(EngineMessage) + case uniqueGift(StarGift.UniqueGift) case profileGift(EnginePeer.Id, ProfileGiftsContext.State.StarGift) case soldOutGift(StarGift.Gift) case upgradePreview([StarGift.UniqueGift.Attribute], String) - var arguments: (peerId: EnginePeer.Id, fromPeerId: EnginePeer.Id?, fromPeerName: String?, messageId: EngineMessage.Id?, incoming: Bool, gift: StarGift, date: Int32, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool, upgraded: Bool, canUpgrade: Bool, upgradeStars: Int64?, transferStars: Int64?, canExportDate: Int32?, upgradeMessageId: Int32?)? { + var arguments: (peerId: EnginePeer.Id?, fromPeerId: EnginePeer.Id?, fromPeerName: String?, messageId: EngineMessage.Id?, incoming: Bool, gift: StarGift, date: Int32, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool, upgraded: Bool, canUpgrade: Bool, upgradeStars: Int64?, transferStars: Int64?, canExportDate: Int32?, upgradeMessageId: Int32?)? { switch self { case let .message(message): if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction { @@ -1793,6 +1806,8 @@ public class GiftViewScreen: ViewControllerComponentContainer { return nil } } + case let .uniqueGift(gift): + return (nil, nil, nil, nil, false, .unique(gift), 0, nil, nil, nil, false, false, false, false, false, nil, nil, nil, nil) case let .profileGift(peerId, gift): return (peerId, gift.fromPeer?.id, gift.fromPeer?.compactDisplayTitle, gift.messageId, false, gift.gift, gift.date, gift.convertStars, gift.text, gift.entities, gift.nameHidden, gift.savedToProfile, false, false, gift.canUpgrade, gift.upgradeStars, gift.transferStars, gift.canExportDate, nil) case .soldOutGift: @@ -1817,7 +1832,8 @@ public class GiftViewScreen: ViewControllerComponentContainer { updateSavedToProfile: ((EngineMessage.Id, Bool) -> Void)? = nil, convertToStars: (() -> Void)? = nil, transferGift: ((Bool, EnginePeer.Id) -> Void)? = nil, - upgradeGift: ((Int64?, Bool) -> Signal)? = nil + upgradeGift: ((Int64?, Bool) -> Signal)? = nil, + shareStory: (() -> Void)? = nil ) { self.context = context self.subject = subject @@ -1831,6 +1847,7 @@ public class GiftViewScreen: ViewControllerComponentContainer { var transferGiftImpl: (() -> Void)? var showAttributeInfoImpl: ((Any, Float) -> Void)? var upgradeGiftImpl: ((Int64?, Bool) -> Signal)? + var openMoreImpl: ((ASDisplayNode, ContextGesture?) -> Void)? var viewUpgradedImpl: ((EngineMessage.Id) -> Void)? super.init( @@ -1865,6 +1882,9 @@ public class GiftViewScreen: ViewControllerComponentContainer { viewUpgraded: { messageId in viewUpgradedImpl?(messageId) }, + openMore: { node, gesture in + openMoreImpl?(node, gesture) + }, showAttributeInfo: { tag, rarity in showAttributeInfoImpl?(tag, rarity) } @@ -2151,6 +2171,66 @@ public class GiftViewScreen: ViewControllerComponentContainer { }) self.present(controller, in: .current) } + + openMoreImpl = { [weak self] node, gesture in + guard let self, let arguments = self.subject.arguments, case let .unique(gift) = arguments.gift else { + return + } + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + //TODO:localize + let link = "https://t.me/nft/\(gift.slug)" + + var items: [ContextMenuItem] = [] + + items.append(.action(ContextMenuActionItem(text: "Copy Link", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, _ in + c?.dismiss(completion: nil) + + guard let self else { + return + } + + UIPasteboard.general.string = link + + self.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + }))) + + items.append(.action(ContextMenuActionItem(text: "Share", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, _ in + c?.dismiss(completion: nil) + + guard let self else { + return + } + let shareController = context.sharedContext.makeShareController( + context: context, + subject: .url(link), + forceExternal: false, + shareStory: shareStory, + actionCompleted: { [weak self] in + self?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + } + ) + self.present(shareController, in: .window(.root)) + }))) + + if let _ = arguments.transferStars { + items.append(.action(ContextMenuActionItem(text: "Transfer", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Replace"), color: theme.contextMenu.primaryColor) + }, action: { c, _ in + c?.dismiss(completion: nil) + + transferGiftImpl?() + }))) + } + + let contextController = ContextController(presentationData: presentationData, source: .reference(GiftViewContextReferenceContentSource(controller: self, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) + self.presentInGlobalOverlay(contextController) + } } required public init(coder aDecoder: NSCoder) { @@ -2298,6 +2378,7 @@ private final class TableComponent: CombinedComponent { let backgroundColor = context.component.theme.actionSheet.opaqueItemBackgroundColor let borderColor = backgroundColor.mixedWith(context.component.theme.list.itemBlocksSeparatorColor, alpha: 0.6) + let secondaryBackgroundColor = context.component.theme.overallDarkAppearance ? context.component.theme.list.itemModalBlocksBackgroundColor : context.component.theme.list.itemInputField.backgroundColor var leftColumnWidth: CGFloat = 0.0 @@ -2388,7 +2469,7 @@ private final class TableComponent: CombinedComponent { if hasLastBackground { let lastRowHeight = rowHeights[i - 1] ?? 0 let lastBackground = lastBackground.update( - component: Rectangle(color: context.component.theme.list.itemInputField.backgroundColor), + component: Rectangle(color: secondaryBackgroundColor), availableSize: CGSize(width: context.availableSize.width, height: lastRowHeight), transition: context.transition ) @@ -2399,7 +2480,7 @@ private final class TableComponent: CombinedComponent { } let leftColumnBackground = leftColumnBackground.update( - component: Rectangle(color: context.component.theme.list.itemInputField.backgroundColor), + component: Rectangle(color: secondaryBackgroundColor), availableSize: CGSize(width: leftColumnWidth, height: innerTotalHeight), transition: context.transition ) @@ -2597,27 +2678,6 @@ private final class PeerCellComponent: Component { } } -private func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? { - return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - context.setFillColor(backgroundColor.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) - - context.setLineWidth(2.0) - context.setLineCap(.round) - context.setStrokeColor(foregroundColor.cgColor) - - context.move(to: CGPoint(x: 10.0, y: 10.0)) - context.addLine(to: CGPoint(x: 20.0, y: 20.0)) - context.strokePath() - - context.move(to: CGPoint(x: 20.0, y: 10.0)) - context.addLine(to: CGPoint(x: 10.0, y: 20.0)) - context.strokePath() - }) -} - private final class ButtonContentComponent: Component { let context: AccountContext let text: String @@ -2914,3 +2974,17 @@ private final class ParagraphComponent: CombinedComponent { } } } + +private final class GiftViewContextReferenceContentSource: ContextReferenceContentSource { + private let controller: ViewController + private let sourceNode: ASDisplayNode + + init(controller: ViewController, sourceNode: ASDisplayNode) { + self.controller = controller + self.sourceNode = sourceNode + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds) + } +} diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/CodableDrawingEntity.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/CodableDrawingEntity.swift index 811350c9c9..241661242e 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/CodableDrawingEntity.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/CodableDrawingEntity.swift @@ -180,6 +180,11 @@ public enum CodableDrawingEntity: Equatable { coordinates: coordinates, messageId: messageId ) + } else if case let .gift(gift, _) = entity.content { + return .starGift( + coordinates: coordinates, + slug: gift.slug + ) } else { return nil } diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingStickerEntity.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingStickerEntity.swift index fa8979408a..13ce46aedd 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingStickerEntity.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingStickerEntity.swift @@ -39,6 +39,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { case video(TelegramMediaFile) case dualVideoReference(Bool) case message([MessageId], CGSize, TelegramMediaFile?, CGRect?, CGFloat?) + case gift(StarGift.UniqueGift, CGSize) public static func == (lhs: Content, rhs: Content) -> Bool { switch lhs { @@ -78,6 +79,12 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { } else { return false } + case let .gift(lhsGift, lhsSize): + if case let .gift(rhsGift, rhsSize) = rhs { + return lhsGift == rhsGift && lhsSize == rhsSize + } else { + return false + } } } } @@ -98,6 +105,9 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { case messageSize case messageMediaRect case messageMediaCornerRadius + + case gift + case referenceDrawingSize case position case scale @@ -120,6 +130,8 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { self.scale = max(0.59, min(1.77, self.scale)) } else if case .message = self.content { self.scale = max(2.5, self.scale) + } else if case .gift = self.content { + self.scale = max(2.5, self.scale) } } } @@ -164,6 +176,8 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { dimensions = CGSize(width: 512.0, height: 512.0) case let .message(_, size, _, _, _): dimensions = size + case let .gift(_, size): + dimensions = size } let boundingSize = CGSize(width: size, height: size) @@ -191,7 +205,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { return true case .dualVideoReference: return true - case .message: + case .message, .gift: return !(self.renderSubEntities ?? []).isEmpty } } @@ -202,7 +216,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { return imageType == .rectangle case .video: return true - case .message: + case .message, .gift: return true default: return false @@ -232,7 +246,10 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.uuid = try container.decode(UUID.self, forKey: .uuid) - if let messageIds = try container.decodeIfPresent([MessageId].self, forKey: .messageIds) { + if let gift = try container.decodeIfPresent(StarGift.UniqueGift.self, forKey: .gift) { + let size = try container.decodeIfPresent(CGSize.self, forKey: .messageSize) ?? .zero + self.content = .gift(gift, size) + } else if let messageIds = try container.decodeIfPresent([MessageId].self, forKey: .messageIds) { let size = try container.decodeIfPresent(CGSize.self, forKey: .messageSize) ?? .zero let file = try container.decodeIfPresent(TelegramMediaFile.self, forKey: .messageFile) let mediaRect = try container.decodeIfPresent(CGRect.self, forKey: .messageMediaRect) @@ -343,6 +360,9 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { try container.encodeIfPresent(file, forKey: .messageFile) try container.encodeIfPresent(mediaRect, forKey: .messageMediaRect) try container.encodeIfPresent(mediaCornerRadius, forKey: .messageMediaCornerRadius) + case let .gift(gift, size): + try container.encode(gift, forKey: .gift) + try container.encode(size, forKey: .messageSize) } try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize) try container.encode(self.position, forKey: .position) diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/DrawingMessageRenderer.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/DrawingMessageRenderer.swift index 482d271297..ca5592222b 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/DrawingMessageRenderer.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/DrawingMessageRenderer.swift @@ -87,17 +87,26 @@ public final class DrawingMessageRenderer { private let isNight: Bool private let isOverlay: Bool private let isLink: Bool + private let isGift: Bool private let messagesContainerNode: ASDisplayNode private var avatarHeaderNode: ListViewItemHeaderNode? private var messageNodes: [ListViewItemNode]? - init(context: AccountContext, messages: [Message], isNight: Bool = false, isOverlay: Bool = false, isLink: Bool = false) { + init( + context: AccountContext, + messages: [Message], + isNight: Bool = false, + isOverlay: Bool = false, + isLink: Bool = false, + isGift: Bool = false + ) { self.context = context self.messages = messages self.isNight = isNight self.isOverlay = isOverlay self.isLink = isLink + self.isGift = isGift self.messagesContainerNode = ASDisplayNode() self.messagesContainerNode.clipsToBounds = true @@ -198,7 +207,13 @@ public final class DrawingMessageRenderer { let avatarHeaderItem: ListViewItemHeader? if let author = self.messages.first?.author { - avatarHeaderItem = self.context.sharedContext.makeChatMessageAvatarHeaderItem(context: self.context, timestamp: self.messages.first?.timestamp ?? 0, peer: self.messages.first!.peers[author.id]!, message: self.messages.first!, theme: theme, strings: presentationData.strings, wallpaper: presentationData.chatWallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: chatBubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder) + let avatarPeer: Peer + if let peer = self.messages.first!.peers[author.id] { + avatarPeer = peer + } else { + avatarPeer = author + } + avatarHeaderItem = self.context.sharedContext.makeChatMessageAvatarHeaderItem(context: self.context, timestamp: self.messages.first?.timestamp ?? 0, peer: avatarPeer, message: self.messages.first!, theme: theme, strings: presentationData.strings, wallpaper: presentationData.chatWallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: chatBubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder) } else { avatarHeaderItem = nil } @@ -208,6 +223,8 @@ public final class DrawingMessageRenderer { var leftInset: CGFloat = 37.0 if self.isLink { leftInset = -6.0 + } else if self.isGift { + leftInset = -50.0 } let containerWidth = layout.size.width - inset * 2.0 let params = ListViewItemLayoutParams(width: containerWidth, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, availableHeight: layout.size.height) @@ -329,13 +346,19 @@ public final class DrawingMessageRenderer { private let nightContainerNode: ContainerNode private let overlayContainerNode: ContainerNode - public init(context: AccountContext, messages: [Message], parentView: UIView, isLink: Bool = false) { + public init( + context: AccountContext, + messages: [Message], + parentView: UIView, + isLink: Bool = false, + isGift: Bool = false + ) { self.context = context self.messages = messages - self.dayContainerNode = ContainerNode(context: context, messages: messages, isLink: isLink) - self.nightContainerNode = ContainerNode(context: context, messages: messages, isNight: true, isLink: isLink) - self.overlayContainerNode = ContainerNode(context: context, messages: messages, isOverlay: true, isLink: isLink) + self.dayContainerNode = ContainerNode(context: context, messages: messages, isLink: isLink, isGift: isGift) + self.nightContainerNode = ContainerNode(context: context, messages: messages, isNight: true, isLink: isLink, isGift: isGift) + self.overlayContainerNode = ContainerNode(context: context, messages: messages, isOverlay: true, isLink: isLink, isGift: isGift) parentView.addSubview(self.dayContainerNode.view) parentView.addSubview(self.nightContainerNode.view) diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift index 38d839bb1c..11bb771608 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift @@ -168,6 +168,7 @@ public final class MediaEditor { case asset(PHAsset) case draft(MediaEditorDraft) case message(MessageId) + case gift(StarGift.UniqueGift) case sticker(TelegramMediaFile) var dimensions: PixelDimensions { @@ -178,7 +179,7 @@ public final class MediaEditor { return PixelDimensions(width: Int32(asset.pixelWidth), height: Int32(asset.pixelHeight)) case let .draft(draft): return draft.dimensions - case .message, .sticker, .videoCollage: + case .message, .gift, .sticker, .videoCollage: return PixelDimensions(width: 1080, height: 1920) } } @@ -817,7 +818,7 @@ public final class MediaEditor { player = self.makePlayer(asset: asset) } } - return getChatWallpaperImage(context: self.context, messageId: messageId) + return getChatWallpaperImage(context: self.context, peerId: messageId.peerId) |> map { _, image, nightImage in return TextureSourceResult( image: image, @@ -828,6 +829,17 @@ public final class MediaEditor { ) } } + case .gift: + textureSource = getChatWallpaperImage(context: self.context, peerId: self.context.account.peerId) + |> map { _, image, nightImage in + return TextureSourceResult( + image: image, + nightImage: nightImage, + player: nil, + playerIsReference: true, + gradientColors: GradientColors(top: .black, bottom: .black) + ) + } case let .sticker(file): let entity = MediaEditorComposerStickerEntity( postbox: self.context.account.postbox, diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift index 4a8440725b..e8f3787677 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift @@ -94,7 +94,7 @@ func composerEntitiesForDrawingEntity(postbox: Postbox, textScale: CGFloat, enti content = .video(file) case .dualVideoReference: return [] - case .message: + case .message, .gift: if let renderImage = entity.renderImage, let image = CIImage(image: renderImage, options: [.colorSpace: colorSpace]) { var entities: [MediaEditorComposerEntity] = [] entities.append(MediaEditorComposerStaticEntity(image: image, position: entity.position, scale: entity.scale, rotation: entity.rotation, baseSize: entity.baseSize, mirrored: false)) diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorUtils.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorUtils.swift index 6277737481..dd77fbaf44 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorUtils.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorUtils.swift @@ -135,7 +135,7 @@ func getTextureImage(device: MTLDevice, texture: MTLTexture, mirror: Bool = fals return UIImage(cgImage: cgImage) } -public func getChatWallpaperImage(context: AccountContext, messageId: EngineMessage.Id) -> Signal<(CGSize, UIImage?, UIImage?), NoError> { +public func getChatWallpaperImage(context: AccountContext, peerId: EnginePeer.Id) -> Signal<(CGSize, UIImage?, UIImage?), NoError> { let themeSettings = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings]) |> map { sharedData -> PresentationThemeSettings in let themeSettings: PresentationThemeSettings @@ -148,7 +148,7 @@ public func getChatWallpaperImage(context: AccountContext, messageId: EngineMess } let peerWallpaper = context.account.postbox.transaction { transaction -> TelegramWallpaper? in - return (transaction.getPeerCachedData(peerId: messageId.peerId) as? CachedChannelData)?.wallpaper + return (transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData)?.wallpaper } return combineLatest(themeSettings, peerWallpaper) diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorDrafts.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorDrafts.swift index 1cba0fc422..29bc6f6f06 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorDrafts.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorDrafts.swift @@ -203,7 +203,7 @@ extension MediaEditorScreenImpl { } else if let image = UIImage(contentsOfFile: draft.fullPath(engine: context.engine)) { innerSaveDraft(media: .image(image: image, dimensions: draft.dimensions)) } - case .message: + case .message, .gift: if let pixel = generateSingleColorImage(size: CGSize(width: 1, height: 1), color: .black) { innerSaveDraft(media: .image(image: pixel, dimensions: PixelDimensions(width: 1080, height: 1920))) } diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index fe43aa0481..6aab728739 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -433,7 +433,6 @@ final class MediaEditorScreenComponent: Component { } }, dismissTextInput: { - }, insertText: { [weak self] text in if let self { @@ -1661,7 +1660,17 @@ final class MediaEditorScreenComponent: Component { var topButtonOffsetX: CGFloat = 0.0 var topButtonOffsetY: CGFloat = 0.0 - if let subject = controller.node.subject, case .message = subject { + var hasDayNightSelection = false + if let subject = controller.node.subject { + switch subject { + case .message, .gift: + hasDayNightSelection = true + default: + break + } + } + + if hasDayNightSelection { let isNightTheme = mediaEditor?.values.nightTheme == true let dayNightContentComponent: AnyComponentWithIdentity @@ -3145,6 +3154,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID isSavingAvailable = true case .message: isSavingAvailable = true + case .gift: + isSavingAvailable = true default: isSavingAvailable = false } @@ -3237,8 +3248,13 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID mediaEditor.seek(initialVideoPosition, andPlay: true) } } - if case .message = subject, self.context.sharedContext.currentPresentationData.with({$0}).autoNightModeTriggered { - mediaEditor.setNightTheme(true) + if self.context.sharedContext.currentPresentationData.with({$0}).autoNightModeTriggered { + switch subject { + case .message, .gift: + mediaEditor.setNightTheme(true) + default: + break + } } mediaEditor.valuesUpdated = { [weak self] values in if let self, let controller = self.controller, values.gradientColors != nil, controller.previousSavedValues != values { @@ -3284,28 +3300,33 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID self.stickerMaskDrawingView?.clearWithEmptyColor() } - if case .message = effectiveSubject { - } else { + switch effectiveSubject { + case .message, .gift: + break + default: self.readyValue.set(.single(true)) } - if case let .image(_, _, additionalImage, position) = effectiveSubject, let additionalImage { - let image = generateImage(CGSize(width: additionalImage.size.width, height: additionalImage.size.width), contextGenerator: { size, context in - let bounds = CGRect(origin: .zero, size: size) - context.clear(bounds) - context.addEllipse(in: bounds) - context.clip() - - if let cgImage = additionalImage.cgImage { - context.draw(cgImage, in: CGRect(origin: CGPoint(x: (size.width - additionalImage.size.width) / 2.0, y: (size.height - additionalImage.size.height) / 2.0), size: additionalImage.size)) - } - }, scale: 1.0) - let imageEntity = DrawingStickerEntity(content: .image(image ?? additionalImage, .dualPhoto)) - imageEntity.referenceDrawingSize = storyDimensions - imageEntity.scale = 1.625 - imageEntity.position = position.getPosition(storyDimensions) - self.entitiesView.add(imageEntity, announce: false) - } else if case let .video(_, _, mirror, additionalVideoPath, _, _, _, changes, position) = effectiveSubject { + switch effectiveSubject { + case let .image(_, _, additionalImage, position): + if let additionalImage { + let image = generateImage(CGSize(width: additionalImage.size.width, height: additionalImage.size.width), contextGenerator: { size, context in + let bounds = CGRect(origin: .zero, size: size) + context.clear(bounds) + context.addEllipse(in: bounds) + context.clip() + + if let cgImage = additionalImage.cgImage { + context.draw(cgImage, in: CGRect(origin: CGPoint(x: (size.width - additionalImage.size.width) / 2.0, y: (size.height - additionalImage.size.height) / 2.0), size: additionalImage.size)) + } + }, scale: 1.0) + let imageEntity = DrawingStickerEntity(content: .image(image ?? additionalImage, .dualPhoto)) + imageEntity.referenceDrawingSize = storyDimensions + imageEntity.scale = 1.625 + imageEntity.position = position.getPosition(storyDimensions) + self.entitiesView.add(imageEntity, announce: false) + } + case let .video(_, _, mirror, additionalVideoPath, _, _, _, changes, position): mediaEditor.setVideoIsMirrored(mirror) if let additionalVideoPath { let videoEntity = DrawingStickerEntity(content: .dualVideoReference(false)) @@ -3324,24 +3345,41 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID } } } - } else if case let .videoCollage(items) = effectiveSubject { + case let .videoCollage(items): mediaEditor.setupCollage(items.map { $0.editorItem }) - } else if case let .message(messageIds) = effectiveSubject { + case let .sticker(_, emoji): + controller.stickerSelectedEmoji = emoji + case .message, .gift: + var isGift = false + let messages: Signal<[Message], NoError> + if case let .message(messageIds) = effectiveSubject { + messages = self.context.engine.data.get( + EngineDataMap(messageIds.map(TelegramEngine.EngineData.Item.Messages.Message.init(id:))) + ) + |> map { result in + var messages: [Message] = [] + for id in messageIds { + if let maybeMessage = result[id], let message = maybeMessage { + messages.append(message._asMessage()) + } + } + return messages + } + } else if case let .gift(gift) = effectiveSubject { + isGift = true + let media: [Media] = [TelegramMediaAction(action: .starGiftUnique(gift: .unique(gift), isUpgrade: false, isTransferred: false, savedToProfile: false, canExportDate: nil, transferStars: nil, isRefunded: false))] + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: self.context.account.peerId, namespace: Namespaces.Message.Cloud, id: -1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: media, peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + messages = .single([message]) + } else { + fatalError() + } + let isNightTheme = mediaEditor.values.nightTheme - let _ = ((self.context.engine.data.get( - EngineDataMap(messageIds.map(TelegramEngine.EngineData.Item.Messages.Message.init(id:))) - )) - |> deliverOnMainQueue).start(next: { [weak self] result in + let _ = (messages + |> deliverOnMainQueue).start(next: { [weak self] messages in guard let self else { return } - var messages: [Message] = [] - for id in messageIds { - if let maybeMessage = result[id], let message = maybeMessage { - messages.append(message._asMessage()) - } - } - var messageFile: TelegramMediaFile? if let maybeFile = messages.first?.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile, maybeFile.isVideo, let _ = self.context.account.postbox.mediaBox.completedResourcePath(maybeFile.resource, pathExtension: nil) { messageFile = maybeFile @@ -3350,7 +3388,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID messageFile = nil } - let renderer = DrawingMessageRenderer(context: self.context, messages: messages, parentView: self.view) + let renderer = DrawingMessageRenderer(context: self.context, messages: messages, parentView: self.view, isGift: isGift) renderer.render(completion: { result in if case .draft = subject, let existingEntityView = self.entitiesView.getView(where: { entityView in if let stickerEntityView = entityView as? DrawingStickerEntityView, case .message = (stickerEntityView.entity as! DrawingStickerEntity).content { @@ -3366,12 +3404,25 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID messageEntity.overlayRenderImage = result.overlayImage existingEntityView.update(animated: false) } else { - let messageEntity = DrawingStickerEntity(content: .message(messageIds, result.size, messageFile, result.mediaFrame?.rect, result.mediaFrame?.cornerRadius)) + var content: DrawingStickerEntity.Content + var position: CGPoint + switch effectiveSubject { + case let .message(messageIds): + content = .message(messageIds, result.size, messageFile, result.mediaFrame?.rect, result.mediaFrame?.cornerRadius) + position = CGPoint(x: storyDimensions.width / 2.0 - 54.0, y: storyDimensions.height / 2.0) + case let .gift(gift): + content = .gift(gift, result.size) + position = CGPoint(x: storyDimensions.width / 2.0, y: storyDimensions.height / 2.0) + default: + fatalError() + } + + let messageEntity = DrawingStickerEntity(content: content) messageEntity.renderImage = result.dayImage messageEntity.secondaryRenderImage = result.nightImage messageEntity.overlayRenderImage = result.overlayImage messageEntity.referenceDrawingSize = storyDimensions - messageEntity.position = CGPoint(x: storyDimensions.width / 2.0 - 54.0, y: storyDimensions.height / 2.0) + messageEntity.position = position let fraction = max(result.size.width, result.size.height) / 353.0 messageEntity.scale = min(6.0, 3.3 * fraction) @@ -3386,10 +3437,10 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID self.readyValue.set(.single(true)) }) }) - } else if case let .sticker(_, emoji) = effectiveSubject { - controller.stickerSelectedEmoji = emoji + default: + break } - + self.gradientColorsDisposable = mediaEditor.gradientColors.start(next: { [weak self] colors in if let self, let colors { let gradientImage = generateGradientImage(size: CGSize(width: 5.0, height: 640.0), colors: colors.array, locations: [0.0, 1.0]) @@ -3749,10 +3800,12 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID } private var canEnhance: Bool { - if case .message = self.subject { + switch self.subject { + case .message, .gift: return false + default: + return true } - return true } private var enhanceInitialTranslation: Float? @@ -3856,8 +3909,13 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID guard !self.isCollageTimelineOpen else { return } - if gestureRecognizer.numberOfTouches == 2, let subject = self.subject, case .message = subject, !self.entitiesView.hasSelection { - return + if gestureRecognizer.numberOfTouches == 2, let subject = self.subject, !self.entitiesView.hasSelection { + switch subject { + case .message, .gift: + return + default: + break + } } let currentTimestamp = CACurrentMediaTime() if let previousPanTimestamp = self.previousPanTimestamp, currentTimestamp - previousPanTimestamp < 0.016, case .changed = gestureRecognizer.state { @@ -3871,8 +3929,13 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID guard !self.isCollageTimelineOpen else { return } - if gestureRecognizer.numberOfTouches == 2, let subject = self.subject, case .message = subject, !self.entitiesView.hasSelection { - return + if gestureRecognizer.numberOfTouches == 2, let subject = self.subject, !self.entitiesView.hasSelection { + switch subject { + case .message, .gift: + return + default: + break + } } let currentTimestamp = CACurrentMediaTime() if let previousPinchTimestamp = self.previousPinchTimestamp, currentTimestamp - previousPinchTimestamp < 0.016, case .changed = gestureRecognizer.state { @@ -3886,8 +3949,13 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID guard !self.isCollageTimelineOpen else { return } - if gestureRecognizer.numberOfTouches == 2, let subject = self.subject, case .message = subject, !self.entitiesView.hasSelection { - return + if gestureRecognizer.numberOfTouches == 2, let subject = self.subject, !self.entitiesView.hasSelection { + switch subject { + case .message, .gift: + return + default: + break + } } let currentTimestamp = CACurrentMediaTime() if let previousRotateTimestamp = self.previousRotateTimestamp, currentTimestamp - previousRotateTimestamp < 0.016, case .changed = gestureRecognizer.state { @@ -4072,7 +4140,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID var animateIn = false if let subject { switch subject { - case .empty, .message, .sticker, .image: + case .empty, .message, .gift, .sticker, .image: animateIn = true default: break @@ -5924,6 +5992,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID case asset(PHAsset) case draft(MediaEditorDraft, Int64?) case message([MessageId]) + case gift(StarGift.UniqueGift) case sticker(TelegramMediaFile, [String]) var dimensions: PixelDimensions { @@ -5936,7 +6005,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID return PixelDimensions(width: Int32(asset.pixelWidth), height: Int32(asset.pixelHeight)) case let .draft(draft, _): return draft.dimensions - case .message, .sticker, .videoCollage: + case .message, .gift, .sticker, .videoCollage: return PixelDimensions(width: 1080, height: 1920) } } @@ -5960,6 +6029,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID return .draft(draft) case let .message(messageIds): return .message(messageIds.first!) + case let .gift(gift): + return .gift(gift) case let .sticker(sticker, _): return .sticker(sticker) } @@ -5985,6 +6056,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID return draft.isVideo case .message: return false + case .gift: + return false case .sticker: return false } @@ -6189,9 +6262,12 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID if self.isEditingStory { needsAudioSession = true } - if case .message = subject { + switch subject { + case .message, .gift: needsAudioSession = true checkPostingAvailability = true + default: + break } if needsAudioSession { self.audioSessionDisposable = self.context.sharedContext.mediaManager.audioSession.push(audioSessionType: .record(speaker: false, video: true, withOthers: true), activate: { _ in @@ -7195,9 +7271,16 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID firstFrame = .single((UIImage(), nil)) } } - case let .message(messages): + case .message, .gift: + let peerId: EnginePeer.Id + if case let .message(messageIds) = subject { + peerId = messageIds.first!.peerId + } else { + peerId = self.context.account.peerId + } + let isNightTheme = mediaEditor.values.nightTheme - let wallpaper = getChatWallpaperImage(context: self.context, messageId: messages.first!) + let wallpaper = getChatWallpaperImage(context: self.context, peerId: peerId) |> map { _, image, nightImage -> UIImage? in if isNightTheme { return nightImage ?? image @@ -8055,7 +8138,18 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID } case let .message(messages): let isNightTheme = mediaEditor.values.nightTheme - exportSubject = getChatWallpaperImage(context: self.context, messageId: messages.first!) + exportSubject = getChatWallpaperImage(context: self.context, peerId: messages.first!.peerId) + |> mapToSignal { _, image, nightImage -> Signal in + if isNightTheme { + let effectiveImage = nightImage ?? image + return effectiveImage.flatMap({ .single(.image(image: $0)) }) ?? .complete() + } else { + return image.flatMap({ .single(.image(image: $0)) }) ?? .complete() + } + } + case .gift: + let isNightTheme = mediaEditor.values.nightTheme + exportSubject = getChatWallpaperImage(context: self.context, peerId: self.context.account.peerId) |> mapToSignal { _, image, nightImage -> Signal in if isNightTheme { let effectiveImage = nightImage ?? image diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index a175b7bbcb..b491063a4d 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -3342,14 +3342,14 @@ final class StoryItemSetContainerSendMessage { useGesturePosition = true let action = { [weak self, weak view, weak controller] in let _ = ((context.engine.messages.getMessagesLoadIfNecessary([messageId], strategy: .cloud(skipLocal: true)) - |> mapToSignal { result -> Signal in + |> mapToSignal { result -> Signal in if case let .result(messages) = result { return .single(messages.first) } else { return .complete() } }) - |> deliverOnMainQueue).startStandalone(next: { [weak self, weak view, weak controller] message in + |> deliverOnMainQueue).startStandalone(next: { [weak self, weak view, weak controller] message in guard let self, let view else { return } @@ -3365,7 +3365,7 @@ final class StoryItemSetContainerSendMessage { switch error { case .privateChannel: let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId)) - |> deliverOnMainQueue).startStandalone(next: { [weak self, weak view] peer in + |> deliverOnMainQueue).startStandalone(next: { [weak self, weak view] peer in guard let self, let view else { return } @@ -3416,6 +3416,31 @@ final class StoryItemSetContainerSendMessage { })) case .weather: return + case let .starGift(_, slug): + useGesturePosition = true + let action = { + let _ = openUserGeneratedUrl(context: component.context, peerId: nil, url: "https://t.me/nft/\(slug)", concealed: false, skipUrlAuth: false, skipConcealedAlert: false, forceDark: true, present: { [weak controller] c in + controller?.present(c, in: .window(.root)) + }, openResolved: { [weak self, weak view] resolved in + guard let self, let view else { + return + } + self.openResolved(view: view, result: resolved, forceExternal: false, concealed: false) + }, alertDisplayUpdated: { [weak self, weak view] alertController in + guard let self, let view else { + return + } + self.statusController = alertController + view.updateIsProgressPaused() + }) + } + if immediate { + action() + return + } + actions.append(ContextMenuAction(content: .textWithIcon(title: updatedPresentationData.initial.strings.Story_ViewGift, icon: generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: .white)), action: { + action() + })) } self.selectedMediaArea = mediaArea diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenStorySharing.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenStorySharing.swift index 91548dcfb2..245bab70ab 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenStorySharing.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenStorySharing.swift @@ -27,10 +27,21 @@ import StoryContainerScreen import SaveToCameraRoll import MediaEditorScreen +enum StorySharingSubject { + case messages([Message]) + case gift(StarGift.UniqueGift) +} + extension ChatControllerImpl { - func openStorySharing(messages: [Message]) { + func openStorySharing(subject: StorySharingSubject) { let context = self.context - let subject: Signal = .single(.message(messages.map { $0.id })) + let editorSubject: Signal + switch subject { + case let .messages(messages): + editorSubject = .single(.message(messages.map { $0.id })) + case let .gift(gift): + editorSubject = .single(.gift(gift)) + } let externalState = MediaEditorTransitionOutExternalState( storyTarget: nil, @@ -42,7 +53,7 @@ extension ChatControllerImpl { let controller = MediaEditorScreenImpl( context: context, mode: .storyEditor, - subject: subject, + subject: editorSubject, transitionIn: nil, transitionOut: { _, _ in return nil diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 4d7ab0dd13..74bd08d89b 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -1198,7 +1198,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.push(controller) return true case .starGift, .starGiftUnique: - let controller = strongSelf.context.sharedContext.makeGiftViewScreen(context: strongSelf.context, message: EngineMessage(message)) + let controller = strongSelf.context.sharedContext.makeGiftViewScreen(context: strongSelf.context, message: EngineMessage(message), shareStory: { [weak self] in + if let self, case let .starGiftUnique(gift, _, _, _, _, _, _) = action.action, case let .unique(uniqueGift) = gift { + Queue.mainQueue().after(0.15) { + self.openStorySharing(subject: .gift(uniqueGift)) + } + } + }) strongSelf.push(controller) return true case .giftStars: diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenMessageShareMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenMessageShareMenu.swift index 619940c612..6954dd1633 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenMessageShareMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenMessageShareMenu.swift @@ -134,7 +134,7 @@ extension ChatControllerImpl { return } Queue.mainQueue().after(0.15) { - self.openStorySharing(messages: messages) + self.openStorySharing(subject: .messages(messages)) } } } diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index 033d37cbfc..37d9bd76f3 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -1211,6 +1211,13 @@ func openResolvedUrlImpl( present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) } }) + case let .collectible(gift): + if let gift { + let controller = context.sharedContext.makeGiftViewScreen(context: context, gift: gift, shareStory: nil) + navigationController?.pushViewController(controller) + } else { + present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) + } case let .messageLink(link): if let link { if let navigationController = navigationController { diff --git a/submodules/TelegramUI/Sources/OpenUrl.swift b/submodules/TelegramUI/Sources/OpenUrl.swift index ed63ab80c3..71230c9e5a 100644 --- a/submodules/TelegramUI/Sources/OpenUrl.swift +++ b/submodules/TelegramUI/Sources/OpenUrl.swift @@ -655,6 +655,22 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur convertedUrl = "https://t.me/addtheme/\(parameter)" } } + } else if parsedUrl.host == "nft" { + if let components = URLComponents(string: "/?" + query) { + var slug: String? + if let queryItems = components.queryItems { + for queryItem in queryItems { + if let value = queryItem.value { + if queryItem.name == "slug" { + slug = value + } + } + } + } + if let slug { + convertedUrl = "https://t.me/nft/\(slug)" + } + } } else if parsedUrl.host == "privatepost" { if let components = URLComponents(string: "/?" + query) { var channelId: Int64? diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index a79b34bd93..f5f17f6630 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -76,6 +76,7 @@ import StarsIntroScreen import ContentReportScreen import AffiliateProgramSetupScreen import GalleryUI +import ShareController private final class AccountUserInterfaceInUseContext { let subscribers = Bag<(Bool) -> Void>() @@ -2922,8 +2923,12 @@ public final class SharedAccountContextImpl: SharedAccountContext { return StarsIntroScreen(context: context) } - public func makeGiftViewScreen(context: AccountContext, message: EngineMessage) -> ViewController { - return GiftViewScreen(context: context, subject: .message(message)) + public func makeGiftViewScreen(context: AccountContext, message: EngineMessage, shareStory: (() -> Void)?) -> ViewController { + return GiftViewScreen(context: context, subject: .message(message), shareStory: shareStory) + } + + public func makeGiftViewScreen(context: AccountContext, gift: StarGift.UniqueGift, shareStory: (() -> Void)?) -> ViewController { + return GiftViewScreen(context: context, subject: .uniqueGift(gift), shareStory: shareStory) } public func makeContentReportScreen(context: AccountContext, subject: ReportContentSubject, forceDark: Bool, present: @escaping (ViewController) -> Void, completion: @escaping () -> Void, requestSelectMessages: ((String, Data, String?) -> Void)?) { @@ -2935,6 +2940,13 @@ public final class SharedAccountContextImpl: SharedAccountContext { }) } + public func makeShareController(context: AccountContext, subject: ShareControllerSubject, forceExternal: Bool, shareStory: (() -> Void)?, actionCompleted: (() -> Void)?) -> ViewController { + let controller = ShareController(context: context, subject: subject, externalShare: forceExternal) + controller.shareStory = shareStory + controller.actionCompleted = actionCompleted + return controller + } + public func makeMiniAppListScreenInitialData(context: AccountContext) -> Signal { return MiniAppListScreen.initialData(context: context) } diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index baf37d9f2b..a0b251be7a 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -105,6 +105,7 @@ public enum ParsedInternalUrl { case chatFolder(slug: String) case premiumGiftCode(slug: String) case messageLink(slug: String) + case collectible(slug: String) case externalUrl(url: String) } @@ -523,6 +524,8 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, context: Accou return .wallpaper(parameter) } else if pathComponents[0] == "addtheme" { return .theme(pathComponents[1]) + } else if pathComponents[0] == "nft" { + return .collectible(slug: pathComponents[1]) } else if pathComponents[0] == "addlist" || pathComponents[0] == "folder" || pathComponents[0] == "list" { return .chatFolder(slug: pathComponents[1]) } else if pathComponents[0] == "boost", pathComponents.count == 2 { @@ -1086,6 +1089,11 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) } case let .premiumGiftCode(slug): return .single(.result(.premiumGiftCode(slug: slug))) + case let .collectible(slug): + return .single(.progress) |> then(context.engine.payments.getUniqueStarGift(slug: slug) + |> map { gift -> ResolveInternalUrlResult in + return .result(.collectible(gift: gift)) + }) case let .messageLink(slug): return .single(.progress) |> then(context.engine.peers.resolveMessageLink(slug: slug)