From c099ec36419dd1613ceba0d5a47673da15da0d3e Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 17 May 2024 23:40:10 +0400 Subject: [PATCH] Stars --- .../Sources/AccountContext.swift | 4 + .../Sources/BotCheckoutController.swift | 6 +- .../ConfettiEffect/Sources/ConfettiView.swift | 32 +- submodules/Display/Source/GenerateImage.swift | 7 + submodules/PremiumUI/BUILD | 1 + submodules/PremiumUI/Resources/star.scn | Bin 0 -> 122671 bytes .../PremiumUI/Sources/BadgeBusinessView.swift | 1 + .../PremiumUI/Sources/BadgeStarsView.swift | 1 + .../BoostHeaderBackgroundComponent.swift | 1 + .../Sources/BusinessPageComponent.swift | 6 +- .../Sources/CreateGiveawayHeaderItem.swift | 1 + .../PremiumUI/Sources/FasterStarsView.swift | 1 + .../Sources/LimitsPageComponent.swift | 6 +- .../Sources/PremiumCoinComponent.swift | 1 + .../Sources/PremiumGiftCodeScreen.swift | 1 + .../PremiumUI/Sources/PremiumGiftScreen.swift | 1 + .../Sources/PremiumIntroScreen.swift | 2 +- .../Sources/StoriesPageComponent.swift | 6 +- .../PremiumUI/Sources/SwirlStarsView.swift | 1 + .../SearchBarNode/Sources/SearchBarNode.swift | 22 +- submodules/TelegramApi/Sources/Api0.swift | 5 +- submodules/TelegramApi/Sources/Api14.swift | 20 +- submodules/TelegramApi/Sources/Api36.swift | 53 + submodules/TelegramApi/Sources/Api7.swift | 50 + .../ApiUtils/StoreMessage_Telegram.swift | 8 +- .../Sources/State/ApplyUpdateMessage.swift | 2 +- .../Sources/State/UpdateMessageService.swift | 4 +- .../Sources/State/UpdatesApiUtils.swift | 12 +- .../TelegramEngine/Payments/Stars.swift | 11 +- .../Payments/TelegramEnginePayments.swift | 10 + .../Sources/Utils/MessageUtils.swift | 4 + .../PresentationResourcesSettings.swift | 24 + .../Sources/ServiceMessageStrings.swift | 13 +- submodules/TelegramUI/BUILD | 3 + .../ChatMessageActionButtonsNode.swift | 24 +- .../Sources/ChatMessageBubbleItemNode.swift | 8 +- .../ChatMessageTextBubbleContentNode.swift | 9 +- .../Sources/ListActionItemComponent.swift | 16 + .../PeerInfoScreen/Sources/PeerInfoData.swift | 34 +- .../Sources/PeerInfoScreen.swift | 32 +- .../Premium/PremiumStarComponent/BUILD | 28 + .../Sources/GiftAvatarComponent.swift | 77 +- .../Sources/PremiumStarComponent.swift | 96 +- .../Sources/ScrollComponent.swift | 8 +- .../Stars/StarsPurchaseScreen/BUILD | 43 + .../Sources/StarsPurchaseScreen.swift | 1052 +++++++++++++++++ .../Stars/StarsTransactionsScreen/BUILD | 45 + .../Sources/StarsBalanceComponent.swift | 163 +++ .../StarsTransactionsListPanelComponent.swift | 553 +++++++++ ...sTransactionsPanelContainerComponent.swift | 795 +++++++++++++ .../Sources/StarsTransactionsScreen.swift | 694 +++++++++++ .../Stars/StarsTransferScreen/BUILD | 39 + .../Sources/StarsTransferScreen.swift | 450 +++++++ .../Premium/Mock.imageset/mock.png | Bin 8299 -> 0 bytes .../Premium/Mock2.imageset/Mock2.png | Bin 112189 -> 0 bytes .../Premium/Stars/Contents.json | 9 + .../Premium/Stars/Star.imageset/Contents.json | 12 + .../Star.imageset/transactionstar_20 (2).pdf | Bin 0 -> 12891 bytes .../StarLarge.imageset}/Contents.json | 2 +- .../StarLarge.imageset/balancestar_48.pdf | Bin 0 -> 14735 bytes .../TopUp.imageset}/Contents.json | 2 +- .../Stars/TopUp.imageset/topbalance.pdf | Bin 0 -> 1761 bytes .../TelegramUI/Sources/ChatController.swift | 51 +- .../TelegramUI/Sources/OpenResolvedUrl.swift | 54 +- .../Sources/SharedAccountContext.swift | 15 + .../WebUI/Sources/WebAppController.swift | 18 +- 66 files changed, 4487 insertions(+), 162 deletions(-) create mode 100644 submodules/PremiumUI/Resources/star.scn create mode 100644 submodules/TelegramUI/Components/Premium/PremiumStarComponent/BUILD rename submodules/{PremiumUI => TelegramUI/Components/Premium/PremiumStarComponent}/Sources/GiftAvatarComponent.swift (85%) rename submodules/{PremiumUI => TelegramUI/Components/Premium/PremiumStarComponent}/Sources/PremiumStarComponent.swift (89%) create mode 100644 submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/BUILD create mode 100644 submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift create mode 100644 submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/BUILD create mode 100644 submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsBalanceComponent.swift create mode 100644 submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift create mode 100644 submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsPanelContainerComponent.swift create mode 100644 submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift create mode 100644 submodules/TelegramUI/Components/Stars/StarsTransferScreen/BUILD create mode 100644 submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift delete mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Mock.imageset/mock.png delete mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Mock2.imageset/Mock2.png create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Stars/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Stars/Star.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Stars/Star.imageset/transactionstar_20 (2).pdf rename submodules/TelegramUI/Images.xcassets/Premium/{Mock2.imageset => Stars/StarLarge.imageset}/Contents.json (75%) create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/balancestar_48.pdf rename submodules/TelegramUI/Images.xcassets/Premium/{Mock.imageset => Stars/TopUp.imageset}/Contents.json (76%) create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Stars/TopUp.imageset/topbalance.pdf diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 604caf29b5..21fb5ed1f5 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1039,6 +1039,10 @@ public protocol SharedAccountContext: AnyObject { func makeMessagesStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, messageId: EngineMessage.Id) -> ViewController func makeStoryStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peerId: EnginePeer.Id, storyId: Int32, storyItem: EngineStoryItem, fromStory: Bool) -> ViewController + func makeStarsTransactionsScreen(context: AccountContext, starsContext: StarsContext) -> ViewController + func makeStarsPurchaseScreen(context: AccountContext, starsContext: StarsContext, options: [StarsTopUpOption], peerId: EnginePeer.Id?, requiredStars: Int32?) -> ViewController + func makeStarsTransferScreen(context: AccountContext, invoice: TelegramMediaInvoice, source: BotPaymentInvoiceSource, inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError>) -> ViewController + func makeDebugSettingsController(context: AccountContext?) -> ViewController? func navigateToCurrentCall() diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift index 3e0c29edef..04b7ea9619 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift @@ -13,9 +13,9 @@ public final class BotCheckoutController: ViewController { case generic } - let form: BotPaymentForm - let validatedFormInfo: BotPaymentValidatedFormInfo? - let botPeer: EnginePeer? + public let form: BotPaymentForm + public let validatedFormInfo: BotPaymentValidatedFormInfo? + public let botPeer: EnginePeer? private init( form: BotPaymentForm, diff --git a/submodules/ConfettiEffect/Sources/ConfettiView.swift b/submodules/ConfettiEffect/Sources/ConfettiView.swift index fb600452bc..961b1d833f 100644 --- a/submodules/ConfettiEffect/Sources/ConfettiView.swift +++ b/submodules/ConfettiEffect/Sources/ConfettiView.swift @@ -43,7 +43,7 @@ public final class ConfettiView: UIView { private var localTime: Float = 0.0 - override public init(frame: CGRect) { + public init(frame: CGRect, customImage: UIImage? = nil) { super.init(frame: frame) self.isUserInteractionEnabled = false @@ -56,19 +56,25 @@ public final class ConfettiView: UIView { ] as [UInt32]).map(UIColor.init(rgb:)) let imageSize = CGSize(width: 8.0, height: 8.0) var images: [(CGImage, CGSize)] = [] - for imageType in 0 ..< 2 { + if let customImage { for color in colors { - if imageType == 0 { - images.append((generateFilledCircleImage(diameter: imageSize.width, color: color)!.cgImage!, imageSize)) - } else { - let spriteSize = CGSize(width: 2.0, height: 6.0) - images.append((generateImage(spriteSize, opaque: false, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(color.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.width))) - context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: size.height - size.width), size: CGSize(width: size.width, height: size.width))) - context.fill(CGRect(origin: CGPoint(x: 0.0, y: size.width / 2.0), size: CGSize(width: size.width, height: size.height - size.width))) - })!.cgImage!, spriteSize)) + images.append((generateTintedImage(image: customImage, color: color)!.cgImage!, customImage.size)) + } + } else { + for imageType in 0 ..< 2 { + for color in colors { + if imageType == 0 { + images.append((generateFilledCircleImage(diameter: imageSize.width, color: color)!.cgImage!, imageSize)) + } else { + let spriteSize = CGSize(width: 2.0, height: 6.0) + images.append((generateImage(spriteSize, opaque: false, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(color.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.width))) + context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: size.height - size.width), size: CGSize(width: size.width, height: size.width))) + context.fill(CGRect(origin: CGPoint(x: 0.0, y: size.width / 2.0), size: CGSize(width: size.width, height: size.height - size.width))) + })!.cgImage!, spriteSize)) + } } } } diff --git a/submodules/Display/Source/GenerateImage.swift b/submodules/Display/Source/GenerateImage.swift index bc7206ceeb..39e93e1263 100644 --- a/submodules/Display/Source/GenerateImage.swift +++ b/submodules/Display/Source/GenerateImage.swift @@ -369,6 +369,9 @@ public func generateGradientTintedImage(image: UIImage?, colors: [UIColor], dire case .diagonal: start = CGPoint(x: 0.0, y: 0.0) end = CGPoint(x: imageRect.width, y: imageRect.height) + case .mirroredDiagonal: + start = CGPoint(x: imageRect.width, y: 0.0) + end = CGPoint(x: 0.0, y: imageRect.height) } context.drawLinearGradient(gradient, start: start, end: end, options: CGGradientDrawingOptions()) @@ -390,6 +393,7 @@ public enum GradientImageDirection { case vertical case horizontal case diagonal + case mirroredDiagonal } public func generateGradientImage(size: CGSize, scale: CGFloat = 0.0, colors: [UIColor], locations: [CGFloat], direction: GradientImageDirection = .vertical) -> UIImage? { @@ -440,6 +444,9 @@ public func generateGradientFilledCircleImage(diameter: CGFloat, colors: NSArray case .diagonal: start = CGPoint(x: 0.0, y: 0.0) end = CGPoint(x: size.width, y: size.height) + case .mirroredDiagonal: + start = CGPoint(x: size.width, y: 0.0) + end = CGPoint(x: 0.0, y: size.height) } context.drawLinearGradient(gradient, start: start, end:end, options: CGGradientDrawingOptions()) diff --git a/submodules/PremiumUI/BUILD b/submodules/PremiumUI/BUILD index 15fcbb0807..9cad24de43 100644 --- a/submodules/PremiumUI/BUILD +++ b/submodules/PremiumUI/BUILD @@ -119,6 +119,7 @@ swift_library( "//submodules/TelegramUI/Components/PremiumPeerShortcutComponent", "//submodules/TelegramUI/Components/EmojiActionIconComponent", "//submodules/TelegramUI/Components/ScrollComponent", + "//submodules/TelegramUI/Components/Premium/PremiumStarComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/PremiumUI/Resources/star.scn b/submodules/PremiumUI/Resources/star.scn new file mode 100644 index 0000000000000000000000000000000000000000..c336915b82bdb1abd1fd08a311b77ba63f97686d GIT binary patch literal 122671 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≦U@nE1oi;7d5ODflqSR^AG7T+Q`O<&}?; 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

yHezyz(E@Fl%&5ifL$4Fvq6!>wm9@Sxx7<`*P{j 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 Void let updatedIsDisplaying: (Bool) -> Void - var resetScroll: ActionSlot? + var resetScroll: ActionSlot? var topContentOffset: CGFloat = 0.0 var bottomContentOffset: CGFloat = 100.0 { @@ -519,7 +519,7 @@ final class BusinessPageComponent: CombinedComponent { self.updatedIsDisplaying(self.isDisplaying) if !self.isDisplaying { - self.resetScroll?.invoke(Void()) + self.resetScroll?.invoke(nil) } } } @@ -566,7 +566,7 @@ final class BusinessPageComponent: CombinedComponent { let topSeparator = Child(Rectangle.self) let title = Child(MultilineTextComponent.self) - let resetScroll = ActionSlot() + let resetScroll = ActionSlot() return { context in let state = context.state diff --git a/submodules/PremiumUI/Sources/CreateGiveawayHeaderItem.swift b/submodules/PremiumUI/Sources/CreateGiveawayHeaderItem.swift index daeb42a2e0..25a3269efc 100644 --- a/submodules/PremiumUI/Sources/CreateGiveawayHeaderItem.swift +++ b/submodules/PremiumUI/Sources/CreateGiveawayHeaderItem.swift @@ -8,6 +8,7 @@ import ItemListUI import PresentationDataUtils import Markdown import ComponentFlow +import PremiumStarComponent final class CreateGiveawayHeaderItem: ItemListControllerHeaderItem { let theme: PresentationTheme diff --git a/submodules/PremiumUI/Sources/FasterStarsView.swift b/submodules/PremiumUI/Sources/FasterStarsView.swift index 967fc2213d..baddaba3d8 100644 --- a/submodules/PremiumUI/Sources/FasterStarsView.swift +++ b/submodules/PremiumUI/Sources/FasterStarsView.swift @@ -4,6 +4,7 @@ import SceneKit import Display import AppBundle import LegacyComponents +import PremiumStarComponent private let sceneVersion: Int = 1 diff --git a/submodules/PremiumUI/Sources/LimitsPageComponent.swift b/submodules/PremiumUI/Sources/LimitsPageComponent.swift index c5ea6cf0ad..c497b6ad57 100644 --- a/submodules/PremiumUI/Sources/LimitsPageComponent.swift +++ b/submodules/PremiumUI/Sources/LimitsPageComponent.swift @@ -453,7 +453,7 @@ final class LimitsPageComponent: CombinedComponent { let updateDismissOffset: (CGFloat) -> Void let updatedIsDisplaying: (Bool) -> Void - var resetScroll: ActionSlot? + var resetScroll: ActionSlot? var topContentOffset: CGFloat = 0.0 var bottomContentOffset: CGFloat = 100.0 { @@ -474,7 +474,7 @@ final class LimitsPageComponent: CombinedComponent { self.updatedIsDisplaying(self.isDisplaying) if !self.isDisplaying { - self.resetScroll?.invoke(Void()) + self.resetScroll?.invoke(nil) } } } @@ -521,7 +521,7 @@ final class LimitsPageComponent: CombinedComponent { let topSeparator = Child(Rectangle.self) let title = Child(MultilineTextComponent.self) - let resetScroll = ActionSlot() + let resetScroll = ActionSlot() return { context in let state = context.state diff --git a/submodules/PremiumUI/Sources/PremiumCoinComponent.swift b/submodules/PremiumUI/Sources/PremiumCoinComponent.swift index ca8926ab1f..851bdd656a 100644 --- a/submodules/PremiumUI/Sources/PremiumCoinComponent.swift +++ b/submodules/PremiumUI/Sources/PremiumCoinComponent.swift @@ -7,6 +7,7 @@ import SceneKit import GZip import AppBundle import LegacyComponents +import PremiumStarComponent private let sceneVersion: Int = 3 diff --git a/submodules/PremiumUI/Sources/PremiumGiftCodeScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftCodeScreen.swift index a6503bac26..fa58f2b3f5 100644 --- a/submodules/PremiumUI/Sources/PremiumGiftCodeScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumGiftCodeScreen.swift @@ -21,6 +21,7 @@ import TextFormat import TelegramStringFormatting import UndoUI import InvisibleInkDustNode +import PremiumStarComponent private final class PremiumGiftCodeSheetContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment diff --git a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift index a1756cc768..e82f316816 100644 --- a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift @@ -21,6 +21,7 @@ import TextFormat import UniversalMediaPlayer import InstantPageCache import ScrollComponent +import PremiumStarComponent extension PremiumGiftSource { var identifier: String? { diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 521ad43d0a..8871eff5c1 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -13,7 +13,6 @@ import SolidRoundedButtonComponent import MultilineTextComponent import MultilineTextWithEntitiesComponent import BundleIconComponent -import SolidRoundedButtonComponent import BlurredBackgroundComponent import Markdown import InAppPurchaseManager @@ -34,6 +33,7 @@ import EmojiStatusComponent import EntityKeyboard import EmojiActionIconComponent import ScrollComponent +import PremiumStarComponent public enum PremiumSource: Equatable { public static func == (lhs: PremiumSource, rhs: PremiumSource) -> Bool { diff --git a/submodules/PremiumUI/Sources/StoriesPageComponent.swift b/submodules/PremiumUI/Sources/StoriesPageComponent.swift index 106d512df0..6b37edbcfb 100644 --- a/submodules/PremiumUI/Sources/StoriesPageComponent.swift +++ b/submodules/PremiumUI/Sources/StoriesPageComponent.swift @@ -516,7 +516,7 @@ final class StoriesPageComponent: CombinedComponent { let updateDismissOffset: (CGFloat) -> Void let updatedIsDisplaying: (Bool) -> Void - var resetScroll: ActionSlot? + var resetScroll: ActionSlot? var topContentOffset: CGFloat = 0.0 var bottomContentOffset: CGFloat = 100.0 { @@ -537,7 +537,7 @@ final class StoriesPageComponent: CombinedComponent { self.updatedIsDisplaying(self.isDisplaying) if !self.isDisplaying { - self.resetScroll?.invoke(Void()) + self.resetScroll?.invoke(nil) } } } @@ -584,7 +584,7 @@ final class StoriesPageComponent: CombinedComponent { let topSeparator = Child(Rectangle.self) let title = Child(MultilineTextComponent.self) - let resetScroll = ActionSlot() + let resetScroll = ActionSlot() return { context in let state = context.state diff --git a/submodules/PremiumUI/Sources/SwirlStarsView.swift b/submodules/PremiumUI/Sources/SwirlStarsView.swift index 8c3ce911b0..d4cddb34f9 100644 --- a/submodules/PremiumUI/Sources/SwirlStarsView.swift +++ b/submodules/PremiumUI/Sources/SwirlStarsView.swift @@ -4,6 +4,7 @@ import SceneKit import Display import AppBundle import SwiftSignalKit +import PremiumStarComponent private let sceneVersion: Int = 1 diff --git a/submodules/SearchBarNode/Sources/SearchBarNode.swift b/submodules/SearchBarNode/Sources/SearchBarNode.swift index 3b9ff80950..f710657cce 100644 --- a/submodules/SearchBarNode/Sources/SearchBarNode.swift +++ b/submodules/SearchBarNode/Sources/SearchBarNode.swift @@ -16,6 +16,10 @@ private func generateLoupeIcon(color: UIColor) -> UIImage? { return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: color) } +private func generateHashtagIcon(color: UIColor) -> UIImage? { + return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Hashtag"), color: color) +} + private func generateClearIcon(color: UIColor) -> UIImage? { return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: color) } @@ -844,6 +848,12 @@ public enum SearchBarStyle { } public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { + public enum Icon { + case loupe + case hashtag + } + public let icon: Icon + public var cancel: (() -> Void)? public var textUpdated: ((String, String?) -> Void)? public var textReturned: ((String) -> Void)? @@ -947,10 +957,11 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { private var strings: PresentationStrings? private let cancelText: String? - public init(theme: SearchBarNodeTheme, strings: PresentationStrings, fieldStyle: SearchBarStyle = .legacy, forceSeparator: Bool = false, displayBackground: Bool = true, cancelText: String? = nil) { + public init(theme: SearchBarNodeTheme, strings: PresentationStrings, fieldStyle: SearchBarStyle = .legacy, icon: Icon = .loupe, forceSeparator: Bool = false, displayBackground: Bool = true, cancelText: String? = nil) { self.fieldStyle = fieldStyle self.forceSeparator = forceSeparator self.cancelText = cancelText + self.icon = icon self.backgroundNode = NavigationBackgroundNode(color: theme.background) self.backgroundNode.isUserInteractionEnabled = false @@ -1036,7 +1047,14 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { self.textBackgroundNode.backgroundColor = theme.inputFill self.textField.textColor = theme.primaryText self.clearButton.setImage(generateClearIcon(color: theme.inputClear), for: []) - self.iconNode.image = generateLoupeIcon(color: theme.inputIcon) + let icon: UIImage? + switch self.icon { + case .loupe: + icon = generateLoupeIcon(color: theme.inputIcon) + case .hashtag: + icon = generateHashtagIcon(color: theme.inputIcon) + } + self.iconNode.image = icon self.textField.keyboardAppearance = theme.keyboard.keyboardAppearance self.textField.tintColor = theme.accent diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 773bf310f3..7bfe51dc5c 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -280,6 +280,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1103040667] = { return Api.ExportedContactToken.parse_exportedContactToken($0) } dict[1571494644] = { return Api.ExportedMessageLink.parse_exportedMessageLink($0) } dict[1070138683] = { return Api.ExportedStoryLink.parse_exportedStoryLink($0) } + dict[-1197736753] = { return Api.FactCheck.parse_factCheck($0) } dict[-207944868] = { return Api.FileHash.parse_fileHash($0) } dict[-11252123] = { return Api.Folder.parse_folder($0) } dict[-373643672] = { return Api.FolderPeer.parse_folderPeer($0) } @@ -515,7 +516,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[340088945] = { return Api.MediaArea.parse_mediaAreaSuggestedReaction($0) } dict[-1098720356] = { return Api.MediaArea.parse_mediaAreaVenue($0) } dict[64088654] = { return Api.MediaAreaCoordinates.parse_mediaAreaCoordinates($0) } - dict[-1109353426] = { return Api.Message.parse_message($0) } + dict[-1808510398] = { return Api.Message.parse_message($0) } dict[-1868117372] = { return Api.Message.parse_messageEmpty($0) } dict[721967202] = { return Api.Message.parse_messageService($0) } dict[-872240531] = { return Api.MessageAction.parse_messageActionBoostApply($0) } @@ -1604,6 +1605,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.ExportedStoryLink: _1.serialize(buffer, boxed) + case let _1 as Api.FactCheck: + _1.serialize(buffer, boxed) case let _1 as Api.FileHash: _1.serialize(buffer, boxed) case let _1 as Api.Folder: diff --git a/submodules/TelegramApi/Sources/Api14.swift b/submodules/TelegramApi/Sources/Api14.swift index dd46a44d24..f9b2095a7c 100644 --- a/submodules/TelegramApi/Sources/Api14.swift +++ b/submodules/TelegramApi/Sources/Api14.swift @@ -316,15 +316,15 @@ public extension Api { } public extension Api { indirect enum Message: TypeConstructorDescription { - case message(flags: Int32, flags2: Int32, id: Int32, fromId: Api.Peer?, fromBoostsApplied: Int32?, peerId: Api.Peer, savedPeerId: Api.Peer?, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, viaBusinessBotId: Int64?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?, quickReplyShortcutId: Int32?, effect: Int64?) + case message(flags: Int32, flags2: Int32, id: Int32, fromId: Api.Peer?, fromBoostsApplied: Int32?, peerId: Api.Peer, savedPeerId: Api.Peer?, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, viaBusinessBotId: Int64?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?, quickReplyShortcutId: Int32?, effect: Int64?, factcheck: Api.FactCheck?) case messageEmpty(flags: Int32, id: Int32, peerId: Api.Peer?) case messageService(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, replyTo: Api.MessageReplyHeader?, date: Int32, action: Api.MessageAction, ttlPeriod: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect): + case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect, let factcheck): if boxed { - buffer.appendInt32(-1109353426) + buffer.appendInt32(-1808510398) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags2, buffer: buffer, boxed: false) @@ -361,6 +361,7 @@ public extension Api { if Int(flags) & Int(1 << 25) != 0 {serializeInt32(ttlPeriod!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 30) != 0 {serializeInt32(quickReplyShortcutId!, buffer: buffer, boxed: false)} if Int(flags2) & Int(1 << 2) != 0 {serializeInt64(effect!, buffer: buffer, boxed: false)} + if Int(flags2) & Int(1 << 3) != 0 {factcheck!.serialize(buffer, true)} break case .messageEmpty(let flags, let id, let peerId): if boxed { @@ -388,8 +389,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect): - return ("message", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("fromId", fromId as Any), ("fromBoostsApplied", fromBoostsApplied as Any), ("peerId", peerId as Any), ("savedPeerId", savedPeerId as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("viaBusinessBotId", viaBusinessBotId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("message", message as Any), ("media", media as Any), ("replyMarkup", replyMarkup as Any), ("entities", entities as Any), ("views", views as Any), ("forwards", forwards as Any), ("replies", replies as Any), ("editDate", editDate as Any), ("postAuthor", postAuthor as Any), ("groupedId", groupedId as Any), ("reactions", reactions as Any), ("restrictionReason", restrictionReason as Any), ("ttlPeriod", ttlPeriod as Any), ("quickReplyShortcutId", quickReplyShortcutId as Any), ("effect", effect as Any)]) + case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect, let factcheck): + return ("message", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("fromId", fromId as Any), ("fromBoostsApplied", fromBoostsApplied as Any), ("peerId", peerId as Any), ("savedPeerId", savedPeerId as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("viaBusinessBotId", viaBusinessBotId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("message", message as Any), ("media", media as Any), ("replyMarkup", replyMarkup as Any), ("entities", entities as Any), ("views", views as Any), ("forwards", forwards as Any), ("replies", replies as Any), ("editDate", editDate as Any), ("postAuthor", postAuthor as Any), ("groupedId", groupedId as Any), ("reactions", reactions as Any), ("restrictionReason", restrictionReason as Any), ("ttlPeriod", ttlPeriod as Any), ("quickReplyShortcutId", quickReplyShortcutId as Any), ("effect", effect as Any), ("factcheck", factcheck as Any)]) case .messageEmpty(let flags, let id, let peerId): return ("messageEmpty", [("flags", flags as Any), ("id", id as Any), ("peerId", peerId as Any)]) case .messageService(let flags, let id, let fromId, let peerId, let replyTo, let date, let action, let ttlPeriod): @@ -474,6 +475,10 @@ public extension Api { if Int(_1!) & Int(1 << 30) != 0 {_26 = reader.readInt32() } var _27: Int64? if Int(_2!) & Int(1 << 2) != 0 {_27 = reader.readInt64() } + var _28: Api.FactCheck? + if Int(_2!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { + _28 = Api.parse(reader, signature: signature) as? Api.FactCheck + } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -501,8 +506,9 @@ public extension Api { let _c25 = (Int(_1!) & Int(1 << 25) == 0) || _25 != nil let _c26 = (Int(_1!) & Int(1 << 30) == 0) || _26 != nil let _c27 = (Int(_2!) & Int(1 << 2) == 0) || _27 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 { - return Api.Message.message(flags: _1!, flags2: _2!, id: _3!, fromId: _4, fromBoostsApplied: _5, peerId: _6!, savedPeerId: _7, fwdFrom: _8, viaBotId: _9, viaBusinessBotId: _10, replyTo: _11, date: _12!, message: _13!, media: _14, replyMarkup: _15, entities: _16, views: _17, forwards: _18, replies: _19, editDate: _20, postAuthor: _21, groupedId: _22, reactions: _23, restrictionReason: _24, ttlPeriod: _25, quickReplyShortcutId: _26, effect: _27) + let _c28 = (Int(_2!) & Int(1 << 3) == 0) || _28 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 { + return Api.Message.message(flags: _1!, flags2: _2!, id: _3!, fromId: _4, fromBoostsApplied: _5, peerId: _6!, savedPeerId: _7, fwdFrom: _8, viaBotId: _9, viaBusinessBotId: _10, replyTo: _11, date: _12!, message: _13!, media: _14, replyMarkup: _15, entities: _16, views: _17, forwards: _18, replies: _19, editDate: _20, postAuthor: _21, groupedId: _22, reactions: _23, restrictionReason: _24, ttlPeriod: _25, quickReplyShortcutId: _26, effect: _27, factcheck: _28) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api36.swift b/submodules/TelegramApi/Sources/Api36.swift index 8e0cc2000b..b6cf49ba95 100644 --- a/submodules/TelegramApi/Sources/Api36.swift +++ b/submodules/TelegramApi/Sources/Api36.swift @@ -4952,6 +4952,22 @@ public extension Api.functions.messages { }) } } +public extension Api.functions.messages { + static func deleteFactCheck(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-774204404) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.deleteFactCheck", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} public extension Api.functions.messages { static func deleteHistory(flags: Int32, peer: Api.InputPeer, maxId: Int32, minDate: Int32?, maxDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -5214,6 +5230,23 @@ public extension Api.functions.messages { }) } } +public extension Api.functions.messages { + static func editFactCheck(peer: Api.InputPeer, msgId: Int32, text: Api.TextWithEntities) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(92925557) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + text.serialize(buffer, true) + return (FunctionDescription(name: "messages.editFactCheck", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("text", String(describing: text))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} public extension Api.functions.messages { static func editInlineBotMessage(flags: Int32, id: Api.InputBotInlineMessageID, message: String?, media: Api.InputMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -5921,6 +5954,26 @@ public extension Api.functions.messages { }) } } +public extension Api.functions.messages { + static func getFactCheck(peer: Api.InputPeer, msgId: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.FactCheck]>) { + let buffer = Buffer() + buffer.appendInt32(-1177696786) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(msgId.count)) + for item in msgId { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.getFactCheck", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.FactCheck]? in + let reader = BufferReader(buffer) + var result: [Api.FactCheck]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.FactCheck.self) + } + return result + }) + } +} public extension Api.functions.messages { static func getFavedStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() diff --git a/submodules/TelegramApi/Sources/Api7.swift b/submodules/TelegramApi/Sources/Api7.swift index 1b56cb7fbb..af25d1b035 100644 --- a/submodules/TelegramApi/Sources/Api7.swift +++ b/submodules/TelegramApi/Sources/Api7.swift @@ -168,6 +168,56 @@ public extension Api { } } +public extension Api { + enum FactCheck: TypeConstructorDescription { + case factCheck(flags: Int32, country: String?, text: Api.TextWithEntities?, hash: Int64) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .factCheck(let flags, let country, let text, let hash): + if boxed { + buffer.appendInt32(-1197736753) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeString(country!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {text!.serialize(buffer, true)} + serializeInt64(hash, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .factCheck(let flags, let country, let text, let hash): + return ("factCheck", [("flags", flags as Any), ("country", country as Any), ("text", text as Any), ("hash", hash as Any)]) + } + } + + public static func parse_factCheck(_ reader: BufferReader) -> FactCheck? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + if Int(_1!) & Int(1 << 1) != 0 {_2 = parseString(reader) } + var _3: Api.TextWithEntities? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } } + var _4: Int64? + _4 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.FactCheck.factCheck(flags: _1!, country: _2, text: _3, hash: _4!) + } + else { + return nil + } + } + + } +} public extension Api { enum FileHash: TypeConstructorDescription { case fileHash(offset: Int64, limit: Int32, hash: Buffer) diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index 1a8628c783..f5f24cff0b 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -126,7 +126,7 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute], func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? { switch messsage { - case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): let chatPeerId = messagePeerId return chatPeerId.peerId case let .messageEmpty(_, _, peerId): @@ -142,7 +142,7 @@ func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? { func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { switch message { - case let .message(_, _, _, fromId, _, chatPeerId, savedPeerId, fwdHeader, viaBotId, viaBusinessBotId, replyTo, _, _, media, _, entities, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, fromId, _, chatPeerId, savedPeerId, fwdHeader, viaBotId, viaBusinessBotId, replyTo, _, _, media, _, entities, _, _, _, _, _, _, _, _, _, _, _, _): let peerId: PeerId = chatPeerId.peerId var result = [peerId] @@ -266,7 +266,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { func apiMessageAssociatedMessageIds(_ message: Api.Message) -> (replyIds: ReferencedReplyMessageIds, generalIds: [MessageId])? { switch message { - case let .message(_, _, id, _, _, chatPeerId, _, _, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, id, _, _, chatPeerId, _, _, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): if let replyTo = replyTo { let peerId: PeerId = chatPeerId.peerId @@ -609,7 +609,7 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes extension StoreMessage { convenience init?(apiMessage: Api.Message, accountPeerId: PeerId, peerIsForum: Bool, namespace: MessageId.Namespace = Namespaces.Message.Cloud) { switch apiMessage { - case let .message(flags, flags2, id, fromId, boosts, chatPeerId, savedPeerId, fwdFrom, viaBotId, viaBusinessBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod, quickReplyShortcutId, messageEffectId): + case let .message(flags, flags2, id, fromId, boosts, chatPeerId, savedPeerId, fwdFrom, viaBotId, viaBusinessBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod, quickReplyShortcutId, messageEffectId, _): let resolvedFromId = fromId?.peerId ?? chatPeerId.peerId var namespace = namespace diff --git a/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift b/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift index d30668fb6a..d53abc3f95 100644 --- a/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift +++ b/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift @@ -96,7 +96,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes var updatedTimestamp: Int32? if let apiMessage = apiMessage { switch apiMessage { - case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): updatedTimestamp = date case .messageEmpty: break diff --git a/submodules/TelegramCore/Sources/State/UpdateMessageService.swift b/submodules/TelegramCore/Sources/State/UpdateMessageService.swift index 09276f544e..9ece0b2395 100644 --- a/submodules/TelegramCore/Sources/State/UpdateMessageService.swift +++ b/submodules/TelegramCore/Sources/State/UpdateMessageService.swift @@ -58,7 +58,7 @@ class UpdateMessageService: NSObject, MTMessageService { self.putNext(groups) } case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities, ttlPeriod): - let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: .peerUser(userId: fromId), fromBoostsApplied: nil, peerId: Api.Peer.peerChat(chatId: chatId), savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil) + let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: .peerUser(userId: fromId), fromBoostsApplied: nil, peerId: Api.Peer.peerChat(chatId: chatId), savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { @@ -74,7 +74,7 @@ class UpdateMessageService: NSObject, MTMessageService { let generatedPeerId = Api.Peer.peerUser(userId: userId) - let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: generatedFromId, fromBoostsApplied: nil, peerId: generatedPeerId, savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil) + let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: generatedFromId, fromBoostsApplied: nil, peerId: generatedPeerId, savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { diff --git a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift index 1cb21c63b5..f938a9ea43 100644 --- a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift +++ b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift @@ -104,7 +104,7 @@ extension Api.MessageMedia { extension Api.Message { var rawId: Int32 { switch self { - case let .message(_, _, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return id case let .messageEmpty(_, id, _): return id @@ -115,7 +115,7 @@ extension Api.Message { func id(namespace: MessageId.Namespace = Namespaces.Message.Cloud) -> MessageId? { switch self { - case let .message(_, _, id, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, id, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): let peerId: PeerId = messagePeerId.peerId return MessageId(peerId: peerId, namespace: namespace, id: id) case let .messageEmpty(_, id, peerId): @@ -132,7 +132,7 @@ extension Api.Message { var peerId: PeerId? { switch self { - case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): let peerId: PeerId = messagePeerId.peerId return peerId case let .messageEmpty(_, _, peerId): @@ -145,7 +145,7 @@ extension Api.Message { var timestamp: Int32? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return date case let .messageService(_, _, _, _, _, date, _, _): return date @@ -156,7 +156,7 @@ extension Api.Message { var preCachedResources: [(MediaResource, Data)]? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return media?.preCachedResources default: return nil @@ -165,7 +165,7 @@ extension Api.Message { var preCachedStories: [StoryId: Api.StoryItem]? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return media?.preCachedStories default: return nil diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift index 0f654a2a25..20722903e4 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift @@ -66,13 +66,13 @@ func _internal_starsTopUpOptions(account: Account) -> Signal<[StarsTopUpOption], } } -private struct InternalStarsStatus { +struct InternalStarsStatus { let balance: Int64 let transactions: [StarsContext.State.Transaction] let nextOffset: String? } -private func requestStarsState(account: Account, peerId: EnginePeer.Id, offset: String?) -> Signal { +func _internal_requestStarsState(account: Account, peerId: EnginePeer.Id, offset: String?) -> Signal { return account.postbox.transaction { transaction -> Peer? in return transaction.getPeer(peerId) } |> mapToSignal { peer -> Signal in @@ -152,6 +152,7 @@ private final class StarsContextImpl { return } self._state = StarsContext.State(balance: balance, transactions: state.transactions) + self.load() }) } @@ -164,12 +165,14 @@ private final class StarsContextImpl { func load() { assert(Queue.mainQueue().isCurrent()) - self.disposable.set((requestStarsState(account: self.account, peerId: self.peerId, offset: nil) + self.disposable.set((_internal_requestStarsState(account: self.account, peerId: self.peerId, offset: nil) |> deliverOnMainQueue).start(next: { [weak self] status in if let self { if let status { self._state = StarsContext.State(balance: status.balance, transactions: status.transactions) self.nextOffset = status.nextOffset + + self.loadMore() } else { self._state = nil } @@ -183,7 +186,7 @@ private final class StarsContextImpl { guard let currentState = self._state, let nextOffset = self.nextOffset else { return } - self.disposable.set((requestStarsState(account: self.account, peerId: self.peerId, offset: nextOffset) + self.disposable.set((_internal_requestStarsState(account: self.account, peerId: self.peerId, offset: nextOffset) |> deliverOnMainQueue).start(next: { [weak self] status in if let self { if let status { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift index 2d68420866..0ab23a9c12 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift @@ -74,6 +74,16 @@ public extension TelegramEngine { return StarsContext(account: self.account, peerId: peerId) } + public func peerStarsState(peerId: EnginePeer.Id) -> Signal { + return _internal_requestStarsState(account: self.account, peerId: peerId, offset: nil) + |> map { state in + guard let state else { + return nil + } + return StarsContext.State(balance: state.balance, transactions: state.transactions) + } + } + public func sendStarsPaymentForm(formId: Int64, source: BotPaymentInvoiceSource) -> Signal { return _internal_sendStarsPaymentForm(account: self.account, formId: formId, source: source) } diff --git a/submodules/TelegramCore/Sources/Utils/MessageUtils.swift b/submodules/TelegramCore/Sources/Utils/MessageUtils.swift index ffc3d978d2..f53cbaa8bf 100644 --- a/submodules/TelegramCore/Sources/Utils/MessageUtils.swift +++ b/submodules/TelegramCore/Sources/Utils/MessageUtils.swift @@ -375,6 +375,10 @@ public extension Message { return false } } + + func isAgeRestricted() -> Bool { + return false + } } public extension Message { diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift index 982808f637..70ae45f085 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift @@ -109,6 +109,30 @@ public struct PresentationResourcesSettings { drawBorder(context: context, rect: bounds) }) + + public static let stars = generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in + let bounds = CGRect(origin: CGPoint(), size: size) + context.clear(bounds) + + let path = UIBezierPath(roundedRect: bounds, cornerRadius: 7.0) + context.addPath(path.cgPath) + context.clip() + + let colorsArray: [CGColor] = [ + UIColor(rgb: 0xfec80f).cgColor, + UIColor(rgb: 0xdd6f12).cgColor + ] + var locations: [CGFloat] = [0.0, 1.0] + let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)! + + context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions()) + + if let image = generateTintedImage(image: UIImage(bundleImageName: "Premium/ButtonIcon"), color: UIColor(rgb: 0xffffff)), let cgImage = image.cgImage { + context.draw(cgImage, in: CGRect(origin: CGPoint(x: floorToScreenPixels((bounds.width - image.size.width) / 2.0), y: floorToScreenPixels((bounds.height - image.size.height) / 2.0)), size: image.size)) + } + + drawBorder(context: context, rect: bounds) + }) public static let passport = renderIcon(name: "Settings/Menu/Passport") public static let watch = renderIcon(name: "Settings/Menu/Watch") diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index a9d665563c..a279c40ad6 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -547,7 +547,18 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, range = (mutableString.string as NSString).range(of: "{amount}") if range.location != NSNotFound { - mutableString.replaceCharacters(in: range, with: NSAttributedString(string: formatCurrencyAmount(totalAmount, currency: currency), font: titleBoldFont, textColor: primaryTextColor)) + if currency == "XTR" { + let amountAttributedString = NSMutableAttributedString(string: " > \(totalAmount)", font: titleBoldFont, textColor: primaryTextColor) + if let range = amountAttributedString.string.range(of: ">"), let starImage = generateScaledImage(image: UIImage(bundleImageName: "Premium/Stars/Star"), size: CGSize(width: 16.0, height: 16.0), opaque: false)?.withRenderingMode(.alwaysTemplate) { + amountAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: amountAttributedString.string)) + amountAttributedString.addAttribute(.foregroundColor, value: primaryTextColor, range: NSRange(range, in: amountAttributedString.string)) + amountAttributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: amountAttributedString.string)) + } + + mutableString.replaceCharacters(in: range, with: amountAttributedString) + } else { + mutableString.replaceCharacters(in: range, with: NSAttributedString(string: formatCurrencyAmount(totalAmount, currency: currency), font: titleBoldFont, textColor: primaryTextColor)) + } } range = (mutableString.string as NSString).range(of: "{name}") if range.location != NSNotFound { diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 11d1da5c76..27a207b85f 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -448,6 +448,9 @@ swift_library( "//submodules/TelegramUI/Components/Settings/BotSettingsScreen", "//submodules/TelegramUI/Components/AdminUserActionsSheet", "//submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview", + "//submodules/TelegramUI/Components/Stars/StarsTransactionsScreen", + "//submodules/TelegramUI/Components/Stars/StarsPurchaseScreen", + "//submodules/TelegramUI/Components/Stars/StarsTransferScreen", ] + select({ "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, "//build-system:ios_sim_arm64": [], diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift index af49b7cd3c..1b24ac7b2c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift @@ -191,7 +191,11 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { case .switchInline: iconImage = incoming ? graphics.chatBubbleActionButtonIncomingShareIconImage : graphics.chatBubbleActionButtonOutgoingShareIconImage case .payment: - iconImage = incoming ? graphics.chatBubbleActionButtonIncomingPaymentIconImage : graphics.chatBubbleActionButtonOutgoingPaymentIconImage + if button.title.contains("Pay XTR") { + iconImage = nil + } else { + iconImage = incoming ? graphics.chatBubbleActionButtonIncomingPaymentIconImage : graphics.chatBubbleActionButtonOutgoingPaymentIconImage + } case .openUserProfile: iconImage = incoming ? graphics.chatBubbleActionButtonIncomingProfileIconImage : graphics.chatBubbleActionButtonOutgoingProfileIconImage case .openWebView: @@ -215,7 +219,23 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { } let messageTheme = incoming ? theme.theme.chat.message.incoming : theme.theme.chat.message.outgoing - let (titleSize, titleApply) = titleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: titleFont, textColor: bubbleVariableColor(variableColor: messageTheme.actionButtonsTextColor, wallpaper: theme.wallpaper)), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(44.0, constrainedWidth - minimumSideInset - minimumSideInset), height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets(top: 1.0, left: 0.0, bottom: 1.0, right: 0.0))) + + let titleColor = bubbleVariableColor(variableColor: messageTheme.actionButtonsTextColor, wallpaper: theme.wallpaper) + let attributedTitle: NSAttributedString + if title.contains("Pay XTR") { + let stars = title.replacingOccurrences(of: "Pay XTR", with: "") + let buttonAttributedString = NSMutableAttributedString(string: "Pay > \(stars)", font: titleFont, textColor: titleColor, paragraphAlignment: .center) + if let range = buttonAttributedString.string.range(of: ">"), let starImage = UIImage(bundleImageName: "Item List/PremiumIcon") { + buttonAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: buttonAttributedString.string)) + buttonAttributedString.addAttribute(.foregroundColor, value: titleColor, range: NSRange(range, in: buttonAttributedString.string)) + buttonAttributedString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: buttonAttributedString.string)) + } + attributedTitle = buttonAttributedString + } else { + attributedTitle = NSAttributedString(string: title, font: titleFont, textColor: titleColor) + } + + let (titleSize, titleApply) = titleLayout(TextNodeLayoutArguments(attributedString: attributedTitle, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(44.0, constrainedWidth - minimumSideInset - minimumSideInset), height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets(top: 1.0, left: 0.0, bottom: 1.0, right: 0.0))) return (titleSize.size.width + sideInset + sideInset, { width in return (CGSize(width: width, height: 42.0), { animation in diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index e651de7a72..14c4fab91b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -208,8 +208,12 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([ if let _ = invoice.extendedMedia { result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default))) } else { - skipText = true - result.append((message, ChatMessageInvoiceBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default))) + if invoice.currency == "XTR" { + result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default))) + } else { + skipText = true + result.append((message, ChatMessageInvoiceBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default))) + } } needReactions = false break inner diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift index 2e71cd7cd8..c6ce0ca329 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift @@ -290,11 +290,14 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { var isSeekableWebMedia = false var isUnsupportedMedia = false var story: Stories.Item? + var invoice: TelegramMediaInvoice? for media in item.message.media { if let file = media as? TelegramMediaFile, let duration = file.duration { mediaDuration = Double(duration) } - if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, webEmbedType(content: content).supportsSeeking { + if let media = media as? TelegramMediaInvoice, media.currency == "XTR" { + invoice = media + } else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, webEmbedType(content: content).supportsSeeking { isSeekableWebMedia = true } else if media is TelegramMediaUnsupported { isUnsupportedMedia = true @@ -308,7 +311,9 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } var isTranslating = false - if let story { + if let invoice { + rawText = invoice.description + } else if let story { rawText = story.text messageEntities = story.entities } else if isUnsupportedMedia { diff --git a/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift b/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift index 11620ebcf9..2e45b46a04 100644 --- a/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift +++ b/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift @@ -113,8 +113,14 @@ public final class ListActionItemComponent: Component { case disabled } + public enum Alignment { + case `default` + case center + } + public let theme: PresentationTheme public let title: AnyComponent + public let titleAlignment: Alignment public let contentInsets: UIEdgeInsets public let leftIcon: LeftIcon? public let icon: Icon? @@ -125,6 +131,7 @@ public final class ListActionItemComponent: Component { public init( theme: PresentationTheme, title: AnyComponent, + titleAlignment: Alignment = .default, contentInsets: UIEdgeInsets = UIEdgeInsets(top: 12.0, left: 0.0, bottom: 12.0, right: 0.0), leftIcon: LeftIcon? = nil, icon: Icon? = nil, @@ -134,6 +141,7 @@ public final class ListActionItemComponent: Component { ) { self.theme = theme self.title = title + self.titleAlignment = titleAlignment self.contentInsets = contentInsets self.leftIcon = leftIcon self.icon = icon @@ -149,6 +157,9 @@ public final class ListActionItemComponent: Component { if lhs.title != rhs.title { return false } + if lhs.titleAlignment != rhs.titleAlignment { + return false + } if lhs.contentInsets != rhs.contentInsets { return false } @@ -373,6 +384,11 @@ public final class ListActionItemComponent: Component { environment: {}, containerSize: CGSize(width: availableSize.width - contentLeftInset - contentRightInset, height: availableSize.height) ) + + if case .center = component.titleAlignment { + contentLeftInset = floor((availableSize.width - titleSize.width) / 2.0) + } + let titleFrame = CGRect(origin: CGPoint(x: contentLeftInset, y: contentHeight), size: titleSize) if let titleView = self.title.view { if titleView.superview == nil { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift index 1736295a98..aa8e3b2029 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift @@ -348,6 +348,7 @@ final class PeerInfoScreenData { let hasSavedMessageTags: Bool let isPremiumRequiredForStoryPosting: Bool let personalChannel: PeerInfoPersonalChannelData? + let starsState: StarsContext.State? let _isContact: Bool var forceIsContact: Bool = false @@ -387,7 +388,8 @@ final class PeerInfoScreenData { accountIsPremium: Bool, hasSavedMessageTags: Bool, isPremiumRequiredForStoryPosting: Bool, - personalChannel: PeerInfoPersonalChannelData? + personalChannel: PeerInfoPersonalChannelData?, + starsState: StarsContext.State? ) { self.peer = peer self.chatPeer = chatPeer @@ -416,6 +418,7 @@ final class PeerInfoScreenData { self.hasSavedMessageTags = hasSavedMessageTags self.isPremiumRequiredForStoryPosting = isPremiumRequiredForStoryPosting self.personalChannel = personalChannel + self.starsState = starsState } } @@ -675,7 +678,7 @@ private func peerInfoPersonalChannel(context: AccountContext, peerId: EnginePeer |> distinctUntilChanged } -func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, accountsAndPeers: Signal<[(AccountContext, EnginePeer, Int32)], NoError>, activeSessionsContextAndCount: Signal<(ActiveSessionsContext, Int, WebSessionsContext)?, NoError>, notificationExceptions: Signal, privacySettings: Signal, archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError>, hasPassport: Signal) -> Signal { +func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, accountsAndPeers: Signal<[(AccountContext, EnginePeer, Int32)], NoError>, activeSessionsContextAndCount: Signal<(ActiveSessionsContext, Int, WebSessionsContext)?, NoError>, notificationExceptions: Signal, privacySettings: Signal, archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError>, hasPassport: Signal, starsContext: StarsContext?) -> Signal { let preferences = context.sharedContext.accountManager.sharedData(keys: [ SharedDataKeys.proxySettings, ApplicationSpecificSharedDataKeys.inAppNotificationSettings, @@ -794,6 +797,13 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, } } + let starsState: Signal + if let starsContext { + starsState = starsContext.state + } else { + starsState = .single(nil) + } + return combineLatest( context.account.viewTracker.peerView(peerId, updateData: true), accountsAndPeers, @@ -818,9 +828,10 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, |> distinctUntilChanged, hasStories, bots, - peerInfoPersonalChannel(context: context, peerId: peerId, isSettings: true) + peerInfoPersonalChannel(context: context, peerId: peerId, isSettings: true), + starsState ) - |> map { peerView, accountsAndPeers, accountSessions, privacySettings, sharedPreferences, notifications, stickerPacks, hasPassport, hasWatchApp, accountPreferences, suggestions, limits, hasPassword, isPowerSavingEnabled, hasStories, bots, personalChannel -> PeerInfoScreenData in + |> map { peerView, accountsAndPeers, accountSessions, privacySettings, sharedPreferences, notifications, stickerPacks, hasPassport, hasWatchApp, accountPreferences, suggestions, limits, hasPassword, isPowerSavingEnabled, hasStories, bots, personalChannel, starsState -> PeerInfoScreenData in let (notificationExceptions, notificationsAuthorizationStatus, notificationsWarningSuppressed) = notifications let (featuredStickerPacks, archivedStickerPacks) = stickerPacks @@ -893,7 +904,8 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, accountIsPremium: peer?.isPremium ?? false, hasSavedMessageTags: false, isPremiumRequiredForStoryPosting: true, - personalChannel: personalChannel + personalChannel: personalChannel, + starsState: starsState ) } } @@ -932,7 +944,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen accountIsPremium: false, hasSavedMessageTags: false, isPremiumRequiredForStoryPosting: true, - personalChannel: nil + personalChannel: nil, + starsState: nil )) case let .user(userPeerId, secretChatId, kind): let groupsInCommon: GroupsInCommonContext? @@ -1259,7 +1272,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen accountIsPremium: accountIsPremium, hasSavedMessageTags: hasSavedMessageTags, isPremiumRequiredForStoryPosting: false, - personalChannel: personalChannel + personalChannel: personalChannel, + starsState: nil ) } case .channel: @@ -1430,7 +1444,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen accountIsPremium: accountIsPremium, hasSavedMessageTags: hasSavedMessageTags, isPremiumRequiredForStoryPosting: isPremiumRequiredForStoryPosting, - personalChannel: nil + personalChannel: nil, + starsState: nil ) } case let .group(groupId): @@ -1724,7 +1739,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen accountIsPremium: accountIsPremium, hasSavedMessageTags: hasSavedMessageTags, isPremiumRequiredForStoryPosting: isPremiumRequiredForStoryPosting, - personalChannel: nil + personalChannel: nil, + starsState: nil )) } } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index f04bd4b412..bd42884a95 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -521,6 +521,7 @@ private enum PeerInfoSettingsSection { case businessSetup case profile case premiumManagement + case stars } private enum PeerInfoReportType { @@ -978,10 +979,20 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 100, label: .text(""), text: presentationData.strings.Settings_Premium, icon: PresentationResourcesSettings.premium, action: { interaction.openSettings(.premium) })) - items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 101, label: .text(""), additionalBadgeLabel: presentationData.strings.Settings_New, text: presentationData.strings.Settings_Business, icon: PresentationResourcesSettings.business, action: { + //TODO:localize + let balanceText: String + if let balance = data.starsState?.balance, balance > 0 { + balanceText = "\(balance)" + } else { + balanceText = "" + } + items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 102, label: .text(balanceText), text: "Your Stars", icon: PresentationResourcesSettings.stars, action: { + interaction.openSettings(.stars) + })) + items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 103, label: .text(""), additionalBadgeLabel: presentationData.strings.Settings_New, text: presentationData.strings.Settings_Business, icon: PresentationResourcesSettings.business, action: { interaction.openSettings(.businessSetup) })) - items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 102, label: .text(""), text: presentationData.strings.Settings_PremiumGift, icon: PresentationResourcesSettings.premiumGift, action: { + items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 104, label: .text(""), text: presentationData.strings.Settings_PremiumGift, icon: PresentationResourcesSettings.premiumGift, action: { interaction.openSettings(.premiumGift) })) } @@ -2521,7 +2532,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } private var didSetReady = false - init(controller: PeerInfoScreenImpl, context: AccountContext, peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], isSettings: Bool, isMyProfile: Bool, hintGroupInCommon: PeerId?, requestsContext: PeerInvitationImportersContext?, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, initialPaneKey: PeerInfoPaneKey?) { + init(controller: PeerInfoScreenImpl, context: AccountContext, peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], isSettings: Bool, isMyProfile: Bool, hintGroupInCommon: PeerId?, requestsContext: PeerInvitationImportersContext?, starsContext: StarsContext?, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, initialPaneKey: PeerInfoPaneKey?) { self.controller = controller self.context = context self.peerId = peerId @@ -4158,7 +4169,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self.cachedFaq.set(.single(nil) |> then(cachedFaqInstantPage(context: self.context) |> map(Optional.init))) - screenData = peerInfoScreenSettingsData(context: context, peerId: peerId, accountsAndPeers: self.accountsAndPeers.get(), activeSessionsContextAndCount: self.activeSessionsContextAndCount.get(), notificationExceptions: self.notificationExceptions.get(), privacySettings: self.privacySettings.get(), archivedStickerPacks: self.archivedPacks.get(), hasPassport: hasPassport) + screenData = peerInfoScreenSettingsData(context: context, peerId: peerId, accountsAndPeers: self.accountsAndPeers.get(), activeSessionsContextAndCount: self.activeSessionsContextAndCount.get(), notificationExceptions: self.notificationExceptions.get(), privacySettings: self.privacySettings.get(), archivedStickerPacks: self.archivedPacks.get(), hasPassport: hasPassport, starsContext: starsContext) self.headerNode.displayCopyContextMenu = { [weak self] node, copyPhone, copyUsername in @@ -9998,6 +10009,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro return } self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: url, forceExternal: !url.hasPrefix("tg://") && !url.contains("?start="), presentationData: self.context.sharedContext.currentPresentationData.with({$0}), navigationController: controller.navigationController as? NavigationController, dismissInput: {}) + case .stars: + if let starsContext = self.controller?.starsContext { + push(self.context.sharedContext.makeStarsTransactionsScreen(context: self.context, starsContext: starsContext)) + } } } @@ -11834,6 +11849,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc private let isMyProfile: Bool private let hintGroupInCommon: PeerId? private weak var requestsContext: PeerInvitationImportersContext? + fileprivate let starsContext: StarsContext? private let switchToRecommendedChannels: Bool private let chatLocation: ChatLocation private let chatLocationContextHolder = Atomic(value: nil) @@ -11912,6 +11928,12 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc self.chatLocation = .peer(id: peerId) } + if isSettings { + self.starsContext = context.engine.payments.peerStarsContext(peerId: context.account.peerId) + } else { + self.starsContext = nil + } + self.presentationData = updatedPresentationData?.0 ?? context.sharedContext.currentPresentationData.with { $0 } let baseNavigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData) @@ -12238,7 +12260,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc } override public func loadDisplayNode() { - self.displayNode = PeerInfoScreenNode(controller: self, context: self.context, peerId: self.peerId, avatarInitiallyExpanded: self.avatarInitiallyExpanded, isOpenedFromChat: self.isOpenedFromChat, nearbyPeerDistance: self.nearbyPeerDistance, reactionSourceMessageId: self.reactionSourceMessageId, callMessages: self.callMessages, isSettings: self.isSettings, isMyProfile: self.isMyProfile, hintGroupInCommon: self.hintGroupInCommon, requestsContext: self.requestsContext, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, initialPaneKey: self.switchToRecommendedChannels ? .recommended : nil) + self.displayNode = PeerInfoScreenNode(controller: self, context: self.context, peerId: self.peerId, avatarInitiallyExpanded: self.avatarInitiallyExpanded, isOpenedFromChat: self.isOpenedFromChat, nearbyPeerDistance: self.nearbyPeerDistance, reactionSourceMessageId: self.reactionSourceMessageId, callMessages: self.callMessages, isSettings: self.isSettings, isMyProfile: self.isMyProfile, hintGroupInCommon: self.hintGroupInCommon, requestsContext: self.requestsContext, starsContext: self.starsContext, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, initialPaneKey: self.switchToRecommendedChannels ? .recommended : nil) self.controllerNode.accountsAndPeers.set(self.accountsAndPeers.get() |> map { $0.1 }) self.controllerNode.activeSessionsContextAndCount.set(self.activeSessionsContextAndCount.get()) self.cachedDataPromise.set(self.controllerNode.cachedDataPromise.get()) diff --git a/submodules/TelegramUI/Components/Premium/PremiumStarComponent/BUILD b/submodules/TelegramUI/Components/Premium/PremiumStarComponent/BUILD new file mode 100644 index 0000000000..580b3ce26b --- /dev/null +++ b/submodules/TelegramUI/Components/Premium/PremiumStarComponent/BUILD @@ -0,0 +1,28 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "PremiumStarComponent", + module_name = "PremiumStarComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/ComponentFlow", + "//submodules/AccountContext", + "//submodules/AppBundle", + "//submodules/GZip", + "//submodules/LegacyComponents", + "//submodules/AvatarNode", + "//submodules/TelegramUI/Components/Chat/MergedAvatarsNode", + "//submodules/Components/MultilineTextComponent:MultilineTextComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/PremiumUI/Sources/GiftAvatarComponent.swift b/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/GiftAvatarComponent.swift similarity index 85% rename from submodules/PremiumUI/Sources/GiftAvatarComponent.swift rename to submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/GiftAvatarComponent.swift index 771982bd65..60e8191424 100644 --- a/submodules/PremiumUI/Sources/GiftAvatarComponent.swift +++ b/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/GiftAvatarComponent.swift @@ -16,38 +16,49 @@ import TelegramPresentationData private let sceneVersion: Int = 1 -final class GiftAvatarComponent: Component { +public final class GiftAvatarComponent: Component { let context: AccountContext let theme: PresentationTheme let peers: [EnginePeer] let isVisible: Bool let hasIdleAnimations: Bool + let hasScaleAnimation: Bool + let color: UIColor? + let offset: CGFloat? - init(context: AccountContext, theme: PresentationTheme, peers: [EnginePeer], isVisible: Bool, hasIdleAnimations: Bool) { + public init(context: AccountContext, theme: PresentationTheme, peers: [EnginePeer], isVisible: Bool, hasIdleAnimations: Bool, hasScaleAnimation: Bool = true, color: UIColor? = nil, offset: CGFloat? = nil) { self.context = context self.theme = theme self.peers = peers self.isVisible = isVisible self.hasIdleAnimations = hasIdleAnimations + self.hasScaleAnimation = hasScaleAnimation + self.color = color + self.offset = offset } - static func ==(lhs: GiftAvatarComponent, rhs: GiftAvatarComponent) -> Bool { - return lhs.peers == rhs.peers && lhs.theme === rhs.theme && lhs.isVisible == rhs.isVisible && lhs.hasIdleAnimations == rhs.hasIdleAnimations + public static func ==(lhs: GiftAvatarComponent, rhs: GiftAvatarComponent) -> Bool { + return lhs.peers == rhs.peers && lhs.theme === rhs.theme && lhs.isVisible == rhs.isVisible && lhs.hasIdleAnimations == rhs.hasIdleAnimations && lhs.hasScaleAnimation == rhs.hasScaleAnimation && lhs.offset == rhs.offset } - final class View: UIView, SCNSceneRendererDelegate, ComponentTaggedView { - final class Tag { + public final class View: UIView, SCNSceneRendererDelegate, ComponentTaggedView { + public final class Tag { + public init() { + + } } - func matches(tag: Any) -> Bool { + public func matches(tag: Any) -> Bool { if let _ = tag as? Tag { return true } return false } + private var component: GiftAvatarComponent? + private var _ready = Promise() - var ready: Signal { + public var ready: Signal { return self._ready.get() } @@ -66,7 +77,7 @@ final class GiftAvatarComponent: Component { private var timer: SwiftSignalKit.Timer? private var hasIdleAnimations = false - override init(frame: CGRect) { + public override init(frame: CGRect) { self.sceneView = SCNView(frame: CGRect(origin: .zero, size: CGSize(width: 64.0, height: 64.0))) self.sceneView.backgroundColor = .clear self.sceneView.transform = CGAffineTransform(scaleX: 0.5, y: 0.5) @@ -80,9 +91,7 @@ final class GiftAvatarComponent: Component { self.addSubview(self.sceneView) self.addSubview(self.avatarNode.view) - - self.setup() - + let tapGestureRecoginzer = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:))) self.addGestureRecognizer(tapGestureRecoginzer) @@ -105,19 +114,43 @@ final class GiftAvatarComponent: Component { self.playAppearanceAnimation(velocity: nil, mirror: false, explode: true) } + private var didSetup = false private func setup() { - guard let scene = loadCompressedScene(name: "gift", version: sceneVersion) else { + guard let scene = loadCompressedScene(name: "gift", version: sceneVersion), !self.didSetup else { return } + self.didSetup = true + self.sceneView.scene = scene self.sceneView.delegate = self - let _ = self.sceneView.snapshot() + if let color = self.component?.color { + let names: [String] = [ + "particles_left", + "particles_right", + "particles_left_bottom", + "particles_right_bottom", + "particles_center" + ] + + for name in names { + if let node = scene.rootNode.childNode(withName: name, recursively: false), let particleSystem = node.particleSystems?.first { + particleSystem.particleColor = color + particleSystem.particleColorVariation = SCNVector4Make(0, 0, 0, 0) + } + } + + self.didSetReady = true + self._ready.set(.single(true)) + self.onReady() + } else { + let _ = self.sceneView.snapshot() + } } private var didSetReady = false - func renderer(_ renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: TimeInterval) { + public func renderer(_ renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: TimeInterval) { if !self.didSetReady { self.didSetReady = true @@ -146,6 +179,10 @@ final class GiftAvatarComponent: Component { } private func setupScaleAnimation() { + guard self.component?.hasScaleAnimation == true else { + return + } + let animation = CABasicAnimation(keyPath: "transform.scale") animation.duration = 2.0 animation.fromValue = 1.0 @@ -245,9 +282,13 @@ final class GiftAvatarComponent: Component { } func update(component: GiftAvatarComponent, availableSize: CGSize, transition: Transition) -> CGSize { + self.component = component + + self.setup() + self.sceneView.bounds = CGRect(origin: .zero, size: CGSize(width: availableSize.width * 2.0, height: availableSize.height * 2.0)) if self.sceneView.superview == self { - self.sceneView.center = CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0) + self.sceneView.center = CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0 + (component.offset ?? 0.0)) } self.hasIdleAnimations = component.hasIdleAnimations @@ -325,11 +366,11 @@ final class GiftAvatarComponent: Component { } } - func makeView() -> View { + public func makeView() -> View { return View(frame: CGRect()) } - func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { return view.update(component: self, availableSize: availableSize, transition: transition) } } diff --git a/submodules/PremiumUI/Sources/PremiumStarComponent.swift b/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/PremiumStarComponent.swift similarity index 89% rename from submodules/PremiumUI/Sources/PremiumStarComponent.swift rename to submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/PremiumStarComponent.swift index baea57bc96..e99e47c3c5 100644 --- a/submodules/PremiumUI/Sources/PremiumStarComponent.swift +++ b/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/PremiumStarComponent.swift @@ -30,22 +30,17 @@ private func generateShineTexture() -> UIImage { return UIImage() } -private func generateDiffuseTexture() -> UIImage { +private func generateDiffuseTexture(colors: [UIColor]) -> UIImage { return generateImage(CGSize(width: 256, height: 256), rotatedContext: { size, context in - let colorsArray: [CGColor] = [ - UIColor(rgb: 0x0079ff).cgColor, - UIColor(rgb: 0x6a93ff).cgColor, - UIColor(rgb: 0x9172fe).cgColor, - UIColor(rgb: 0xe46acd).cgColor, - ] + let colorsArray: [CGColor] = colors.map { $0.cgColor } var locations: [CGFloat] = [0.0, 0.25, 0.5, 0.75, 1.0] let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)! - context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions()) + context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: size.height), end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions()) })! } -func loadCompressedScene(name: String, version: Int) -> SCNScene? { +public func loadCompressedScene(name: String, version: Int) -> SCNScene? { let resourceUrl: URL if let url = getAppBundle().url(forResource: name, withExtension: "scn") { resourceUrl = url @@ -69,40 +64,52 @@ func loadCompressedScene(name: String, version: Int) -> SCNScene? { return scene } -final class PremiumStarComponent: Component { +public final class PremiumStarComponent: Component { let isIntro: Bool let isVisible: Bool let hasIdleAnimations: Bool - - init(isIntro: Bool, isVisible: Bool, hasIdleAnimations: Bool) { + let colors: [UIColor]? + + public init( + isIntro: Bool, + isVisible: Bool, + hasIdleAnimations: Bool, + colors: [UIColor]? = nil + ) { self.isIntro = isIntro self.isVisible = isVisible self.hasIdleAnimations = hasIdleAnimations + self.colors = colors } - static func ==(lhs: PremiumStarComponent, rhs: PremiumStarComponent) -> Bool { - return lhs.isIntro == rhs.isIntro && lhs.isVisible == rhs.isVisible && lhs.hasIdleAnimations == rhs.hasIdleAnimations + public static func ==(lhs: PremiumStarComponent, rhs: PremiumStarComponent) -> Bool { + return lhs.isIntro == rhs.isIntro && lhs.isVisible == rhs.isVisible && lhs.hasIdleAnimations == rhs.hasIdleAnimations && lhs.colors == rhs.colors } - final class View: UIView, SCNSceneRendererDelegate, ComponentTaggedView { - final class Tag { + public final class View: UIView, SCNSceneRendererDelegate, ComponentTaggedView { + public final class Tag { + public init() { + + } } - func matches(tag: Any) -> Bool { + public func matches(tag: Any) -> Bool { if let _ = tag as? Tag { return true } return false } + private var component: PremiumStarComponent? + private var _ready = Promise() - var ready: Signal { + public var ready: Signal { return self._ready.get() } - weak var animateFrom: UIView? - weak var containerView: UIView? - var animationColor: UIColor? + public weak var animateFrom: UIView? + public weak var containerView: UIView? + public var animationColor: UIColor? private let sceneView: SCNView @@ -126,8 +133,6 @@ final class PremiumStarComponent: Component { self.addSubview(self.sceneView) - self.setup() - let panGestureRecoginzer = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(_:))) self.addGestureRecognizer(panGestureRecoginzer) @@ -274,21 +279,46 @@ final class PremiumStarComponent: Component { } } + private var didSetup = false private func setup() { - guard let scene = loadCompressedScene(name: "star", version: sceneVersion) else { + guard !self.didSetup, let scene = loadCompressedScene(name: "star", version: sceneVersion) else { return } + self.didSetup = true self.sceneView.scene = scene self.sceneView.delegate = self - self.didSetReady = true - self._ready.set(.single(true)) - self.onReady() + if let node = scene.rootNode.childNode(withName: "star", recursively: false), let colors = self.component?.colors, let color = colors.first { + node.geometry?.materials.first?.diffuse.contents = generateDiffuseTexture(colors: colors) + + let names: [String] = [ + "particles_left", + "particles_right", + "particles_left_bottom", + "particles_right_bottom", + "particles_center" + ] + + for name in names { + if let node = scene.rootNode.childNode(withName: name, recursively: false), let particleSystem = node.particleSystems?.first { + particleSystem.particleColor = color + particleSystem.particleColorVariation = SCNVector4Make(0, 0, 0, 0) + } + } + } + + if self.animateFrom != nil { + let _ = self.sceneView.snapshot() + } else { + self.didSetReady = true + self._ready.set(.single(true)) + self.onReady() + } } private var didSetReady = false - func renderer(_ renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: TimeInterval) { + public func renderer(_ renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: TimeInterval) { if !self.didSetReady { self.didSetReady = true @@ -305,7 +335,7 @@ final class PremiumStarComponent: Component { } containerView = containerView.subviews[2].subviews[1] - + if let animationColor = self.animationColor { let newNode = node.clone() newNode.geometry = node.geometry?.copy() as? SCNGeometry @@ -562,6 +592,10 @@ final class PremiumStarComponent: Component { } func update(component: PremiumStarComponent, availableSize: CGSize, transition: Transition) -> CGSize { + self.component = component + + self.setup() + self.sceneView.bounds = CGRect(origin: .zero, size: CGSize(width: availableSize.width * 2.0, height: availableSize.height * 2.0)) if self.sceneView.superview == self { self.sceneView.center = CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0) @@ -573,11 +607,11 @@ final class PremiumStarComponent: Component { } } - func makeView() -> View { + public func makeView() -> View { return View(frame: CGRect(), isIntro: self.isIntro) } - func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { return view.update(component: self, availableSize: availableSize, transition: transition) } } diff --git a/submodules/TelegramUI/Components/ScrollComponent/Sources/ScrollComponent.swift b/submodules/TelegramUI/Components/ScrollComponent/Sources/ScrollComponent.swift index f9421338cb..9f4cb768d6 100644 --- a/submodules/TelegramUI/Components/ScrollComponent/Sources/ScrollComponent.swift +++ b/submodules/TelegramUI/Components/ScrollComponent/Sources/ScrollComponent.swift @@ -35,7 +35,7 @@ public final class ScrollComponent: Component { let contentInsets: UIEdgeInsets let contentOffsetUpdated: (_ top: CGFloat, _ bottom: CGFloat) -> Void let contentOffsetWillCommit: (UnsafeMutablePointer) -> Void - let resetScroll: ActionSlot + let resetScroll: ActionSlot public init( content: AnyComponent<(ChildEnvironment, ScrollChildEnvironment)>, @@ -43,7 +43,7 @@ public final class ScrollComponent: Component { contentInsets: UIEdgeInsets, contentOffsetUpdated: @escaping (_ top: CGFloat, _ bottom: CGFloat) -> Void, contentOffsetWillCommit: @escaping (UnsafeMutablePointer) -> Void, - resetScroll: ActionSlot = ActionSlot() + resetScroll: ActionSlot = ActionSlot() ) { self.content = content self.externalState = externalState @@ -120,8 +120,8 @@ public final class ScrollComponent: Component { ) transition.setFrame(view: self.contentView, frame: CGRect(origin: .zero, size: contentSize), completion: nil) - component.resetScroll.connect { [weak self] _ in - self?.setContentOffset(.zero, animated: false) + component.resetScroll.connect { [weak self] point in + self?.setContentOffset(point ?? .zero, animated: point != nil) } if self.contentSize != contentSize { diff --git a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/BUILD b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/BUILD new file mode 100644 index 0000000000..91f04b157a --- /dev/null +++ b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/BUILD @@ -0,0 +1,43 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "StarsPurchaseScreen", + module_name = "StarsPurchaseScreen", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/ComponentFlow", + "//submodules/Components/ViewControllerComponent", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/Components/MultilineTextComponent", + "//submodules/Components/BalancedTextComponent", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/AppBundle", + "//submodules/ItemListUI", + "//submodules/TelegramStringFormatting", + "//submodules/PresentationDataUtils", + "//submodules/Components/SheetComponent", + "//submodules/UndoUI", + "//submodules/TextFormat", + "//submodules/TelegramUI/Components/ListSectionComponent", + "//submodules/TelegramUI/Components/ListActionItemComponent", + "//submodules/TelegramUI/Components/ScrollComponent", + "//submodules/TelegramUI/Components/Premium/PremiumStarComponent", + "//submodules/Components/BlurredBackgroundComponent", + "//submodules/Components/BundleIconComponent", + "//submodules/ConfettiEffect", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift new file mode 100644 index 0000000000..a4791b830e --- /dev/null +++ b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift @@ -0,0 +1,1052 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import SwiftSignalKit +import TelegramCore +import Postbox +import TelegramPresentationData +import PresentationDataUtils +import ViewControllerComponent +import AccountContext +import MultilineTextComponent +import BalancedTextComponent +import Markdown +import InAppPurchaseManager +import AnimationCache +import MultiAnimationRenderer +import UndoUI +import TelegramStringFormatting +import ListSectionComponent +import ListActionItemComponent +import ScrollComponent +import BlurredBackgroundComponent +import TextFormat +import PremiumStarComponent +import BundleIconComponent +import ConfettiEffect + +private struct StarsProduct: Equatable { + let option: StarsTopUpOption + let storeProduct: InAppPurchaseManager.Product + + var id: String { + return self.storeProduct.id + } + + var price: String { + return self.storeProduct.price + } +} + +private final class StarsPurchaseScreenContentComponent: CombinedComponent { + typealias EnvironmentType = (ViewControllerComponentContainer.Environment, ScrollChildEnvironment) + + let context: AccountContext + let options: [StarsTopUpOption] + let peerId: EnginePeer.Id? + let requiredStars: Int32? + let forceDark: Bool + let products: [StarsProduct]? + let expanded: Bool + let stateUpdated: (Transition) -> Void + let buy: (StarsProduct) -> Void + + init( + context: AccountContext, + options: [StarsTopUpOption], + peerId: EnginePeer.Id?, + requiredStars: Int32?, + forceDark: Bool, + products: [StarsProduct]?, + expanded: Bool, + stateUpdated: @escaping (Transition) -> Void, + buy: @escaping (StarsProduct) -> Void + ) { + self.context = context + self.options = options + self.peerId = peerId + self.requiredStars = requiredStars + self.forceDark = forceDark + self.products = products + self.expanded = expanded + self.stateUpdated = stateUpdated + self.buy = buy + } + + static func ==(lhs: StarsPurchaseScreenContentComponent, rhs: StarsPurchaseScreenContentComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.options != rhs.options { + return false + } + if lhs.peerId != rhs.peerId { + return false + } + if lhs.requiredStars != rhs.requiredStars { + return false + } + if lhs.forceDark != rhs.forceDark { + return false + } + if lhs.products != rhs.products { + return false + } + if lhs.expanded != rhs.expanded { + return false + } + return true + } + + final class State: ComponentState { + private let context: AccountContext + + var products: [StarsProduct]? + var peer: EnginePeer? + + private var disposable: Disposable? + + var cachedChevronImage: (UIImage, PresentationTheme)? + + init( + context: AccountContext, + peerId: EnginePeer.Id? + ) { + self.context = context + + super.init() + + if let peerId { + self.disposable = (context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) + ) + |> deliverOnMainQueue).start(next: { [weak self] peer in + if let self, let peer { + self.peer = peer + self.updated(transition: .immediate) + } + }) + } + + let _ = updatePremiumPromoConfigurationOnce(account: context.account).start() + } + + deinit { + self.disposable?.dispose() + } + } + + func makeState() -> State { + return State(context: self.context, peerId: self.peerId) + } + + static var body: Body { + let overscroll = Child(Rectangle.self) + let fade = Child(RoundedRectangle.self) + let text = Child(BalancedTextComponent.self) + let list = Child(VStack.self) + let termsText = Child(BalancedTextComponent.self) + + return { context in + let sideInset: CGFloat = 16.0 + + let scrollEnvironment = context.environment[ScrollChildEnvironment.self].value + let environment = context.environment[ViewControllerComponentContainer.Environment.self].value + let state = context.state + state.products = context.component.products + + let theme = environment.theme + let presentationData = context.component.context.sharedContext.currentPresentationData.with { $0 } + + let availableWidth = context.availableSize.width + let sideInsets = sideInset * 2.0 + environment.safeInsets.left + environment.safeInsets.right + var size = CGSize(width: context.availableSize.width, height: 0.0) + + var topBackgroundColor = theme.list.plainBackgroundColor + let bottomBackgroundColor = theme.list.blocksBackgroundColor + if theme.overallDarkAppearance { + topBackgroundColor = bottomBackgroundColor + } + + let overscroll = overscroll.update( + component: Rectangle(color: topBackgroundColor), + availableSize: CGSize(width: context.availableSize.width, height: 1000), + transition: context.transition + ) + context.add(overscroll + .position(CGPoint(x: overscroll.size.width / 2.0, y: -overscroll.size.height / 2.0)) + ) + + let fade = fade.update( + component: RoundedRectangle( + colors: [ + topBackgroundColor, + bottomBackgroundColor + ], + cornerRadius: 0.0, + gradientDirection: .vertical + ), + availableSize: CGSize(width: availableWidth, height: 300), + transition: context.transition + ) + context.add(fade + .position(CGPoint(x: fade.size.width / 2.0, y: fade.size.height / 2.0)) + ) + + size.height += 183.0 + 10.0 + environment.navigationHeight - 56.0 + + let textColor = theme.list.itemPrimaryTextColor + let accentColor = theme.list.itemAccentColor + + let textFont = Font.regular(15.0) + let boldTextFont = Font.semibold(15.0) + + //TODO:localize + let textString: String +// if let peer = state.peer, let requiredStars = context.component.requiredStars { +// textString = "\(peer.compactDisplayTitle) requests \(requiredStars) Stars.\n\nAvailable balance: **1000 Stars**.\n\nBuy **Stars** to unlock **content and services** in miniapps on **Telegram**." +// } else { + textString = "Choose how many Stars you would like to buy." +// } + + let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: accentColor), linkAttribute: { contents in + return (TelegramTextAttributes.URL, contents) + }) + + let text = text.update( + component: BalancedTextComponent( + text: .markdown( + text: textString, + attributes: markdownAttributes + ), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.2, + highlightColor: environment.theme.list.itemAccentColor.withAlphaComponent(0.2), + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) + } else { + return nil + } + }, + tapAction: { _, _ in + } + ), + environment: {}, + availableSize: CGSize(width: availableWidth - sideInsets - 8.0, height: 240.0), + transition: .immediate + ) + context.add(text + .position(CGPoint(x: size.width / 2.0, y: size.height + text.size.height / 2.0)) + .appear(.default(alpha: true)) + .disappear(.default(alpha: true)) + ) + size.height += text.size.height + size.height += 21.0 + + let initialValues: [Int64] = [ + 15, + 75, + 250, + 500, + 1000, + 2500 + ] + + let stars: [Int64: Int] = [ + 15: 1, + 75: 2, + 250: 3, + 500: 4, + 1000: 5, + 2500: 6, + 25: 1, + 50: 1, + 100: 2, + 150: 2, + 350: 3, + 750: 4, + 1500: 5 + ] + + let externalStateUpdated = context.component.stateUpdated + let layoutPerks = { + size.height += 8.0 + + var i = 0 + var items: [AnyComponentWithIdentity] = [] + + guard let products = state.products else { + return + } + for product in products { + if !context.component.expanded && !initialValues.contains(product.option.count) { + continue + } + + let title = "\(product.option.count) Stars" + let price = product.price + + let titleComponent = AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: title, + font: Font.medium(presentationData.listsFontSize.baseDisplaySize), + textColor: environment.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 0 + )) + + let buy = context.component.buy + items.append(AnyComponentWithIdentity( + id: product.id, + component: AnyComponent(ListSectionComponent( + theme: environment.theme, + header: nil, + footer: nil, + items: [AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( + theme: environment.theme, + title: titleComponent, + contentInsets: UIEdgeInsets(top: 12.0, left: -6.0, bottom: 12.0, right: 0.0), + leftIcon: .custom(AnyComponentWithIdentity(id: 0, component: AnyComponent(StarsIconComponent( + count: stars[product.option.count] ?? 1 + )))), + accessory: .custom(ListActionItemComponent.CustomAccessory(component: AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: price, + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: environment.theme.list.itemSecondaryTextColor + )), + maximumNumberOfLines: 0 + ))), insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 16.0))), + action: { _ in + buy(product) + } + )))] + )) + )) + i += 1 + } + + if !context.component.expanded { + let titleComponent = AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: "Show More Options", + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: environment.theme.list.itemAccentColor + )), + horizontalAlignment: .center, + maximumNumberOfLines: 0 + )) + + let titleCombinedComponent = AnyComponent(HStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: titleComponent), + AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(BundleIconComponent(name: "Chat/Input/Search/DownButton", tintColor: environment.theme.list.itemAccentColor))) + ], spacing: 1.0)) + + items.append(AnyComponentWithIdentity( + id: items.count, + component: AnyComponent(ListSectionComponent( + theme: environment.theme, + header: nil, + footer: nil, + items: [AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( + theme: environment.theme, + title: titleCombinedComponent, + titleAlignment: .center, + contentInsets: UIEdgeInsets(top: 7.0, left: 0.0, bottom: 7.0, right: 0.0), + leftIcon: nil, + accessory: .none, + action: { _ in + externalStateUpdated(.easeInOut(duration: 0.3)) + } + )))] + )) + )) + } + + let list = list.update( + component: VStack(items, spacing: 16.0), + environment: {}, + availableSize: CGSize(width: availableWidth - sideInsets, height: .greatestFiniteMagnitude), + transition: context.transition + ) + context.add(list + .position(CGPoint(x: availableWidth / 2.0, y: size.height + list.size.height / 2.0)) + ) + size.height += list.size.height + + size.height += 23.0 + } + + layoutPerks() + + + let termsFont = Font.regular(13.0) + let termsTextColor = environment.theme.list.freeTextColor + let termsMarkdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: termsFont, textColor: termsTextColor), bold: MarkdownAttributeSet(font: termsFont, textColor: termsTextColor), link: MarkdownAttributeSet(font: termsFont, textColor: environment.theme.list.itemAccentColor), linkAttribute: { contents in + return (TelegramTextAttributes.URL, contents) + }) + let textSideInset: CGFloat = 16.0 + + let termsText = termsText.update( + component: BalancedTextComponent( + text: .markdown(text: "By proceeding and purchasing Stars, you agree with [Terms and Conditions]().", attributes: termsMarkdownAttributes), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.2, + highlightColor: environment.theme.list.itemAccentColor.withAlphaComponent(0.2), + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) + } else { + return nil + } + }, + tapAction: { attributes, _ in + + } + ), + environment: {}, + availableSize: CGSize(width: availableWidth - sideInsets - textSideInset * 3.0, height: .greatestFiniteMagnitude), + transition: context.transition + ) + context.add(termsText + .position(CGPoint(x: availableWidth / 2.0, y: size.height + termsText.size.height / 2.0)) + ) + size.height += termsText.size.height + size.height += 10.0 + + size.height += scrollEnvironment.insets.bottom + + return size + } + } +} + +private final class StarsPurchaseScreenComponent: CombinedComponent { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let starsContext: StarsContext + let options: [StarsTopUpOption] + let peerId: EnginePeer.Id? + let requiredStars: Int32? + let forceDark: Bool + let updateInProgress: (Bool) -> Void + let present: (ViewController) -> Void + let completion: () -> Void + + init( + context: AccountContext, + starsContext: StarsContext, + options: [StarsTopUpOption], + peerId: EnginePeer.Id?, + requiredStars: Int32?, + forceDark: Bool, + updateInProgress: @escaping (Bool) -> Void, + present: @escaping (ViewController) -> Void, + completion: @escaping () -> Void + ) { + self.context = context + self.starsContext = starsContext + self.options = options + self.peerId = peerId + self.requiredStars = requiredStars + self.forceDark = forceDark + self.updateInProgress = updateInProgress + self.present = present + self.completion = completion + } + + static func ==(lhs: StarsPurchaseScreenComponent, rhs: StarsPurchaseScreenComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.starsContext !== rhs.starsContext { + return false + } + if lhs.options != rhs.options { + return false + } + if lhs.peerId != rhs.peerId { + return false + } + if lhs.requiredStars != rhs.requiredStars { + return false + } + if lhs.forceDark != rhs.forceDark { + return false + } + return true + } + + final class State: ComponentState { + private let context: AccountContext + private let updateInProgress: (Bool) -> Void + private let present: (ViewController) -> Void + private let completion: () -> Void + + var topContentOffset: CGFloat? + var bottomContentOffset: CGFloat? + + var hasIdleAnimations = true + + var inProgress = false + + private(set) var promoConfiguration: PremiumPromoConfiguration? + + private(set) var products: [StarsProduct]? + private(set) var starsState: StarsContext.State? + + let animationCache: AnimationCache + let animationRenderer: MultiAnimationRenderer + + private var disposable: Disposable? + private var paymentDisposable = MetaDisposable() + + var cachedChevronImage: (UIImage, PresentationTheme)? + + init( + context: AccountContext, + starsContext: StarsContext, + initialOptions: [StarsTopUpOption], + updateInProgress: @escaping (Bool) -> Void, + present: @escaping (ViewController) -> Void, + completion: @escaping () -> Void + ) { + self.context = context + self.updateInProgress = updateInProgress + self.present = present + self.completion = completion + + self.animationCache = context.animationCache + self.animationRenderer = context.animationRenderer + + super.init() + + let availableProducts: Signal<[InAppPurchaseManager.Product], NoError> + if let inAppPurchaseManager = context.inAppPurchaseManager { + availableProducts = inAppPurchaseManager.availableProducts + } else { + availableProducts = .single([]) + } + + let options: Signal<[StarsTopUpOption], NoError> + if !initialOptions.isEmpty { + options = .single(initialOptions) + } else { + options = .single([]) |> then(context.engine.payments.starsTopUpOptions()) + } + + self.disposable = combineLatest( + queue: Queue.mainQueue(), + availableProducts, + options, + starsContext.state + ).start(next: { [weak self] availableProducts, options, starsState in + guard let self else { + return + } + var products: [StarsProduct] = [] + for option in options { + if let product = availableProducts.first(where: { $0.id == option.storeProductId }) { + products.append(StarsProduct(option: option, storeProduct: product)) + } + } + + self.products = products.sorted(by: { $0.option.count < $1.option.count }) + self.starsState = starsState + + self.updated(transition: .immediate) + }) + } + + deinit { + self.disposable?.dispose() + self.paymentDisposable.dispose() + } + + func buy(product: StarsProduct) { + guard let inAppPurchaseManager = self.context.inAppPurchaseManager, !self.inProgress else { + return + } + + self.inProgress = true + self.updateInProgress(true) + self.updated(transition: .immediate) + + let (currency, amount) = product.storeProduct.priceCurrencyAndAmount + let purpose: AppStoreTransactionPurpose = .stars(count: product.option.count, currency: currency, amount: amount) + + let _ = (self.context.engine.payments.canPurchasePremium(purpose: purpose) + |> deliverOnMainQueue).start(next: { [weak self] available in + if let strongSelf = self { + let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } + if available { + strongSelf.paymentDisposable.set((inAppPurchaseManager.buyProduct(product.storeProduct, purpose: purpose) + |> deliverOnMainQueue).start(next: { [weak self] status in + if let self, case .purchased = status { + self.inProgress = false + self.updateInProgress(false) + + self.updated(transition: .easeInOut(duration: 0.25)) + self.completion() + } + }, error: { [weak self] error in + if let strongSelf = self { + strongSelf.inProgress = false + strongSelf.updateInProgress(false) + strongSelf.updated(transition: .immediate) + + var errorText: String? + switch error { + case .generic: + errorText = presentationData.strings.Premium_Purchase_ErrorUnknown + case .network: + errorText = presentationData.strings.Premium_Purchase_ErrorNetwork + case .notAllowed: + errorText = presentationData.strings.Premium_Purchase_ErrorNotAllowed + case .cantMakePayments: + errorText = presentationData.strings.Premium_Purchase_ErrorCantMakePayments + case .assignFailed: + errorText = presentationData.strings.Premium_Purchase_ErrorUnknown + case .cancelled: + break + } + + if let errorText = errorText { + let alertController = textAlertController(context: strongSelf.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) + strongSelf.present(alertController) + } + } + })) + } else { + strongSelf.inProgress = false + strongSelf.updateInProgress(false) + strongSelf.updated(transition: .immediate) + } + } + }) + } + + func updateIsFocused(_ isFocused: Bool) { + self.hasIdleAnimations = !isFocused + self.updated(transition: .immediate) + } + + var isExpanded = false + } + + func makeState() -> State { + return State(context: self.context, starsContext: self.starsContext, initialOptions: self.options, updateInProgress: self.updateInProgress, present: self.present, completion: self.completion) + } + + static var body: Body { + let background = Child(Rectangle.self) + let scrollContent = Child(ScrollComponent.self) + let star = Child(PremiumStarComponent.self) + let topPanel = Child(BlurredBackgroundComponent.self) + let topSeparator = Child(Rectangle.self) + let title = Child(MultilineTextComponent.self) + let balanceText = Child(MultilineTextComponent.self) + + let scrollAction = ActionSlot() + + return { context in + let environment = context.environment[EnvironmentType.self].value + let state = context.state + + let background = background.update(component: Rectangle(color: environment.theme.list.blocksBackgroundColor), environment: {}, availableSize: context.availableSize, transition: context.transition) + + var starIsVisible = true + if let topContentOffset = state.topContentOffset, topContentOffset >= 123.0 { + starIsVisible = false + } + + let header = star.update( + component: PremiumStarComponent( + isIntro: true, + isVisible: starIsVisible, + hasIdleAnimations: state.hasIdleAnimations, + colors: [ + UIColor(rgb: 0xea8904), + UIColor(rgb: 0xf09903), + UIColor(rgb: 0xfec209), + UIColor(rgb: 0xfed31a) + ] + ), + availableSize: CGSize(width: min(414.0, context.availableSize.width), height: 220.0), + transition: context.transition + ) + + let topPanel = topPanel.update( + component: BlurredBackgroundComponent( + color: environment.theme.rootController.navigationBar.blurredBackgroundColor + ), + availableSize: CGSize(width: context.availableSize.width, height: environment.navigationHeight), + transition: context.transition + ) + + let topSeparator = topSeparator.update( + component: Rectangle( + color: environment.theme.rootController.navigationBar.separatorColor + ), + availableSize: CGSize(width: context.availableSize.width, height: UIScreenPixel), + transition: context.transition + ) + + //TODO:localize + let title = title.update( + component: MultilineTextComponent( + text: .plain(NSAttributedString(string: "Get Stars", font: Font.bold(28.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor)), + horizontalAlignment: .center, + truncationType: .end, + maximumNumberOfLines: 1 + ), + availableSize: context.availableSize, + transition: context.transition + ) + + let textColor = environment.theme.list.itemPrimaryTextColor + let accentColor = UIColor(rgb: 0x597cf5) + + let textFont = Font.regular(14.0) + let boldTextFont = Font.bold(14.0) + let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: accentColor), linkAttribute: { _ in + return nil + }) + + if state.cachedChevronImage == nil || state.cachedChevronImage?.1 !== environment.theme { + state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: UIColor(rgb: 0xf09903))!, environment.theme) + } + + let balanceAttributedString = parseMarkdownIntoAttributedString("Balance: * **\(state.starsState?.balance ?? 0)**", attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString + if let range = balanceAttributedString.string.range(of: "*"), let chevronImage = state.cachedChevronImage?.0 { + balanceAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: balanceAttributedString.string)) + balanceAttributedString.addAttribute(.foregroundColor, value: UIColor(rgb: 0xf09903), range: NSRange(range, in: balanceAttributedString.string)) + balanceAttributedString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: balanceAttributedString.string)) + } + let balanceText = balanceText.update( + component: MultilineTextComponent( + text: .plain(balanceAttributedString), + horizontalAlignment: .left, + maximumNumberOfLines: 0 + ), + availableSize: CGSize(width: 200, height: context.availableSize.height), + transition: .immediate + ) + + let scrollContent = scrollContent.update( + component: ScrollComponent( + content: AnyComponent(StarsPurchaseScreenContentComponent( + context: context.component.context, + options: context.component.options, + peerId: context.component.peerId, + requiredStars: context.component.requiredStars, + forceDark: context.component.forceDark, + products: state.products, + expanded: state.isExpanded, + stateUpdated: { [weak state] transition in + scrollAction.invoke(CGPoint(x: 0.0, y: 176.0)) + state?.isExpanded = true + state?.updated(transition: transition) + }, + buy: { [weak state] product in + state?.buy(product: product) + } + )), + contentInsets: UIEdgeInsets(top: environment.navigationHeight, left: 0.0, bottom: environment.safeInsets.bottom, right: 0.0), + contentOffsetUpdated: { [weak state] topContentOffset, bottomContentOffset in + state?.topContentOffset = topContentOffset + state?.bottomContentOffset = bottomContentOffset + Queue.mainQueue().justDispatch { + state?.updated(transition: .immediate) + } + }, + contentOffsetWillCommit: { targetContentOffset in + if targetContentOffset.pointee.y < 100.0 { + targetContentOffset.pointee = CGPoint(x: 0.0, y: 0.0) + } else if targetContentOffset.pointee.y < 176.0 { + targetContentOffset.pointee = CGPoint(x: 0.0, y: 176.0) + } + }, + resetScroll: scrollAction + ), + environment: { environment }, + availableSize: context.availableSize, + transition: context.transition + ) + + let topInset: CGFloat = environment.navigationHeight - 56.0 + + context.add(background + .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) + ) + + context.add(scrollContent + .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) + ) + + let topPanelAlpha: CGFloat + let titleOffset: CGFloat + let titleScale: CGFloat + let titleOffsetDelta = (topInset + 160.0) - (environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0) + let titleAlpha: CGFloat + + if let topContentOffset = state.topContentOffset { + topPanelAlpha = min(20.0, max(0.0, topContentOffset - 95.0)) / 20.0 + let topContentOffset = topContentOffset + max(0.0, min(1.0, topContentOffset / titleOffsetDelta)) * 10.0 + titleOffset = topContentOffset + let fraction = max(0.0, min(1.0, titleOffset / titleOffsetDelta)) + titleScale = 1.0 - fraction * 0.36 + + titleAlpha = 1.0 + } else { + topPanelAlpha = 0.0 + titleScale = 1.0 + titleOffset = 0.0 + titleAlpha = 1.0 + } + + context.add(header + .position(CGPoint(x: context.availableSize.width / 2.0, y: topInset + header.size.height / 2.0 - 30.0 - titleOffset * titleScale)) + .scale(titleScale) + ) + + context.add(topPanel + .position(CGPoint(x: context.availableSize.width / 2.0, y: topPanel.size.height / 2.0)) + .opacity(topPanelAlpha) + ) + context.add(topSeparator + .position(CGPoint(x: context.availableSize.width / 2.0, y: topPanel.size.height)) + .opacity(topPanelAlpha) + ) + + context.add(title + .position(CGPoint(x: context.availableSize.width / 2.0, y: max(topInset + 160.0 - titleOffset, environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0))) + .scale(titleScale) + .opacity(titleAlpha) + ) + + context.add(balanceText + .position(CGPoint(x: context.availableSize.width - 16.0 - balanceText.size.width / 2.0, y: 28.0)) + ) + + return context.availableSize + } + } +} + +public final class StarsPurchaseScreen: ViewControllerComponentContainer { + fileprivate let context: AccountContext + fileprivate let starsContext: StarsContext + fileprivate let options: [StarsTopUpOption] + + private var didSetReady = false + private let _ready = Promise() + public override var ready: Promise { + return self._ready + } + + public init( + context: AccountContext, + starsContext: StarsContext, + options: [StarsTopUpOption], + peerId: EnginePeer.Id?, + requiredStars: Int32?, + modal: Bool = true, + forceDark: Bool = false + ) { + self.context = context + self.starsContext = starsContext + self.options = options + + var updateInProgressImpl: ((Bool) -> Void)? + var presentImpl: ((ViewController) -> Void)? + var completionImpl: (() -> Void)? + super.init(context: context, component: StarsPurchaseScreenComponent( + context: context, + starsContext: starsContext, + options: options, + peerId: peerId, + requiredStars: requiredStars, + forceDark: forceDark, + updateInProgress: { inProgress in + updateInProgressImpl?(inProgress) + }, + present: { c in + presentImpl?(c) + }, + completion: { + completionImpl?() + } + ), navigationBarAppearance: .transparent, presentationMode: modal ? .modal : .default, theme: forceDark ? .dark : .default) + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + if modal { + let cancelItem = UIBarButtonItem(title: presentationData.strings.Common_Close, style: .plain, target: self, action: #selector(self.cancelPressed)) + self.navigationItem.setLeftBarButton(cancelItem, animated: false) + self.navigationPresentation = .modal + } else { + self.navigationPresentation = .modalInLargeLayout + } + + updateInProgressImpl = { [weak self] inProgress in + if let strongSelf = self { + strongSelf.navigationItem.leftBarButtonItem?.isEnabled = !inProgress + strongSelf.view.disablesInteractiveTransitionGestureRecognizer = inProgress + strongSelf.view.disablesInteractiveModalDismiss = inProgress + } + } + presentImpl = { [weak self] c in + if let self { + self.present(c, in: .window(.root)) + } + } + completionImpl = { [weak self] in + if let self { + self.animateSuccess() + } + } + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + self.dismissAllTooltips() + } + + fileprivate func dismissAllTooltips() { + self.window?.forEachController({ controller in + if let controller = controller as? UndoOverlayController { + controller.dismiss() + } + }) + self.forEachController({ controller in + if let controller = controller as? UndoOverlayController { + controller.dismiss() + } + return true + }) + } + + @objc private func cancelPressed() { + self.dismiss() + self.wasDismissed?() + } + + public func animateSuccess() { + self.dismiss() + self.navigationController?.view.addSubview(ConfettiView(frame: self.view.bounds, customImage: UIImage(bundleImageName: "Peer Info/PremiumIcon"))) + } + + public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + if !self.didSetReady { + if let view = self.node.hostView.findTaggedView(tag: PremiumStarComponent.View.Tag()) as? PremiumStarComponent.View { + self.didSetReady = true + self._ready.set(view.ready) + } + } + } +} + +func generateStarsIcon(count: Int) -> UIImage { + let image = generateGradientTintedImage( + image: UIImage(bundleImageName: "Peer Info/PremiumIcon"), + colors: [ + UIColor(rgb: 0xfed219), + UIColor(rgb: 0xf3a103), + UIColor(rgb: 0xe78104) + ], + direction: .diagonal + )! + + let imageSize = CGSize(width: 20.0, height: 20.0) + let partImage = generateImage(imageSize, contextGenerator: { size, context in + context.clear(CGRect(origin: .zero, size: size)) + if let cgImage = image.cgImage { + context.draw(cgImage, in: CGRect(origin: .zero, size: size), byTiling: false) + context.saveGState() + context.clip(to: CGRect(origin: .zero, size: size).insetBy(dx: -1.0, dy: -1.0).offsetBy(dx: -2.0, dy: 0.0), mask: cgImage) + + context.setBlendMode(.clear) + context.setFillColor(UIColor.clear.cgColor) + context.fill(CGRect(origin: .zero, size: size)) + context.restoreGState() + + context.setBlendMode(.clear) + context.setFillColor(UIColor.clear.cgColor) + context.fill(CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width / 2.0, height: size.height - 4.0))) + } + })! + + let spacing: CGFloat = (3.0 - UIScreenPixel) + let totalWidth = 20.0 + spacing * CGFloat(count - 1) + + return generateImage(CGSize(width: ceil(totalWidth), height: 20.0), contextGenerator: { size, context in + context.clear(CGRect(origin: .zero, size: size)) + + var originX = floorToScreenPixels((size.width - totalWidth) / 2.0) + + if let cgImage = image.cgImage, let partCGImage = partImage.cgImage { + context.draw(cgImage, in: CGRect(origin: CGPoint(x: originX, y: 0.0), size: imageSize), byTiling: false) + originX += spacing + + for _ in 0 ..< count - 1 { + context.draw(partCGImage, in: CGRect(origin: CGPoint(x: originX, y: 0.0), size: imageSize), byTiling: false) + originX += spacing + } + } + })! +} + +final class StarsIconComponent: CombinedComponent { + let count: Int + + init( + count: Int + ) { + self.count = count + } + + static func ==(lhs: StarsIconComponent, rhs: StarsIconComponent) -> Bool { + if lhs.count != rhs.count { + return false + } + return true + } + + static var body: Body { + let icon = Child(Image.self) + + var image: (UIImage, Int)? + + return { context in + if image == nil || image?.1 != context.component.count { + image = (generateStarsIcon(count: context.component.count), context.component.count) + } + + let iconSize = CGSize(width: image!.0.size.width, height: 20.0) + + let icon = icon.update( + component: Image(image: image?.0), + availableSize: iconSize, + transition: context.transition + ) + + let iconPosition = CGPoint(x: iconSize.width / 2.0, y: iconSize.height / 2.0) + context.add(icon + .position(iconPosition) + ) + return iconSize + } + } +} diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/BUILD b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/BUILD new file mode 100644 index 0000000000..3a16bf04b3 --- /dev/null +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/BUILD @@ -0,0 +1,45 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "StarsTransactionsScreen", + module_name = "StarsTransactionsScreen", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/ComponentFlow", + "//submodules/Components/ViewControllerComponent", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/Components/MultilineTextComponent", + "//submodules/Components/BalancedTextComponent", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/AppBundle", + "//submodules/ItemListUI", + "//submodules/TelegramStringFormatting", + "//submodules/PresentationDataUtils", + "//submodules/Components/SheetComponent", + "//submodules/UndoUI", + "//submodules/TextFormat", + "//submodules/TelegramUI/Components/ListSectionComponent", + "//submodules/TelegramUI/Components/ListActionItemComponent", + "//submodules/TelegramUI/Components/ScrollComponent", + "//submodules/TelegramUI/Components/Premium/PremiumStarComponent", + "//submodules/Components/BlurredBackgroundComponent", + "//submodules/Components/BundleIconComponent", + "//submodules/Components/SolidRoundedButtonComponent", + "//submodules/TelegramUI/Components/AnimatedTextComponent", + "//submodules/AvatarNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsBalanceComponent.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsBalanceComponent.swift new file mode 100644 index 0000000000..28ac40019e --- /dev/null +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsBalanceComponent.swift @@ -0,0 +1,163 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import ComponentFlow +import AccountContext +import MultilineTextComponent +import TelegramPresentationData +import PresentationDataUtils +import SolidRoundedButtonComponent +import AnimatedTextComponent + +final class StarsBalanceComponent: Component { + let theme: PresentationTheme + let strings: PresentationStrings + let count: Int64 + let buy: () -> Void + + init( + theme: PresentationTheme, + strings: PresentationStrings, + count: Int64, + buy: @escaping () -> Void + ) { + self.theme = theme + self.strings = strings + self.count = count + self.buy = buy + } + + static func ==(lhs: StarsBalanceComponent, rhs: StarsBalanceComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.count != rhs.count { + return false + } + return true + } + + final class View: UIView { + private let icon = UIImageView() + private let title = ComponentView() + private let subtitle = ComponentView() + private var button = ComponentView() + + private var component: StarsBalanceComponent? + + override init(frame: CGRect) { + super.init(frame: frame) + + self.icon.image = UIImage(bundleImageName: "Premium/Stars/StarLarge") + + self.addSubview(self.icon) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: StarsBalanceComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.component = component + + let sideInset: CGFloat = 16.0 + + let size = CGSize(width: availableSize.width, height: 172.0) + + var animatedTextItems: [AnimatedTextComponent.Item] = [] + animatedTextItems.append(AnimatedTextComponent.Item( + id: 1, + isUnbreakable: true, + content: .number(Int(component.count), minDigits: 1) + )) + + let titleSize = self.title.update( + transition: .easeInOut(duration: 0.2), + component: AnyComponent( + AnimatedTextComponent( + font: Font.with(size: 48.0, design: .round, weight: .semibold), + color: component.theme.list.itemPrimaryTextColor, + items: animatedTextItems + ) +// MultilineTextComponent( +// text: .plain(NSAttributedString(string: "\(component.count)", font: Font.with(size: 48.0, design: .round, weight: .semibold), textColor: component.theme.list.itemPrimaryTextColor)), +// horizontalAlignment: .center +// ) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0) + ) + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + if let icon = self.icon.image { + let spacing: CGFloat = 3.0 + let totalWidth = titleSize.width + icon.size.width + spacing + let origin = floorToScreenPixels((availableSize.width - totalWidth) / 2.0) + let titleFrame = CGRect(origin: CGPoint(x: origin + icon.size.width + spacing, y: 13.0), size: titleSize) + titleView.frame = titleFrame + + self.icon.frame = CGRect(origin: CGPoint(x: origin, y: 18.0), size: icon.size) + } + } + + let subtitleSize = self.subtitle.update( + transition: .immediate, + component: AnyComponent( + MultilineTextComponent( + text: .plain(NSAttributedString(string: "your balance", font: Font.regular(17.0), textColor: component.theme.list.itemSecondaryTextColor)), + horizontalAlignment: .center + ) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0) + ) + if let subtitleView = self.subtitle.view { + if subtitleView.superview == nil { + self.addSubview(subtitleView) + } + let subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - subtitleSize.width) / 2.0), y: 70.0), size: subtitleSize) + subtitleView.frame = subtitleFrame + } + + let buttonSize = self.button.update( + transition: .immediate, + component: AnyComponent( + SolidRoundedButtonComponent( + title: "Buy More Stars", + theme: SolidRoundedButtonComponent.Theme(theme: component.theme), + height: 50.0, + cornerRadius: 11.0, + action: { [weak self] in + self?.component?.buy() + } + ) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0) + ) + if let buttonView = self.button.view { + if buttonView.superview == nil { + self.addSubview(buttonView) + } + let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: size.height - buttonSize.height - sideInset), size: buttonSize) + buttonView.frame = buttonFrame + } + + return size + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift new file mode 100644 index 0000000000..b51cac1047 --- /dev/null +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift @@ -0,0 +1,553 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import ComponentFlow +import SwiftSignalKit +import ViewControllerComponent +import ComponentDisplayAdapters +import TelegramPresentationData +import AccountContext +import TelegramCore +import MultilineTextComponent +import ListActionItemComponent +import TelegramStringFormatting +import AvatarNode +import BundleIconComponent + +final class StarsTransactionsListPanelComponent: Component { + typealias EnvironmentType = StarsTransactionsPanelEnvironment + + final class Item: Equatable { + let transaction: StarsContext.State.Transaction + + init( + transaction: StarsContext.State.Transaction + ) { + self.transaction = transaction + } + + static func ==(lhs: Item, rhs: Item) -> Bool { + if lhs.transaction != rhs.transaction { + return false + } + return true + } + } + + final class Items: Equatable { + let items: [Item] + + init(items: [Item]) { + self.items = items + } + + static func ==(lhs: Items, rhs: Items) -> Bool { + if lhs === rhs { + return true + } + return lhs.items == rhs.items + } + } + + let context: AccountContext + let items: Items? + let action: (StarsContext.State.Transaction) -> Void + + init( + context: AccountContext, + items: Items?, + action: @escaping (StarsContext.State.Transaction) -> Void + ) { + self.context = context + self.items = items + self.action = action + } + + static func ==(lhs: StarsTransactionsListPanelComponent, rhs: StarsTransactionsListPanelComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.items != rhs.items { + return false + } + return true + } + + private struct ItemLayout: Equatable { + let containerInsets: UIEdgeInsets + let containerWidth: CGFloat + let itemHeight: CGFloat + let itemCount: Int + + let contentHeight: CGFloat + + init( + containerInsets: UIEdgeInsets, + containerWidth: CGFloat, + itemHeight: CGFloat, + itemCount: Int + ) { + self.containerInsets = containerInsets + self.containerWidth = containerWidth + self.itemHeight = itemHeight + self.itemCount = itemCount + + self.contentHeight = containerInsets.top + containerInsets.bottom + CGFloat(itemCount) * itemHeight + } + + func visibleItems(for rect: CGRect) -> Range? { + let offsetRect = rect.offsetBy(dx: -self.containerInsets.left, dy: -self.containerInsets.top) + var minVisibleRow = Int(floor((offsetRect.minY) / (self.itemHeight))) + minVisibleRow = max(0, minVisibleRow) + let maxVisibleRow = Int(ceil((offsetRect.maxY) / (self.itemHeight))) + + let minVisibleIndex = minVisibleRow + let maxVisibleIndex = maxVisibleRow + + if maxVisibleIndex >= minVisibleIndex { + return minVisibleIndex ..< (maxVisibleIndex + 1) + } else { + return nil + } + } + + func itemFrame(for index: Int) -> CGRect { + return CGRect(origin: CGPoint(x: 0.0, y: self.containerInsets.top + CGFloat(index) * self.itemHeight), size: CGSize(width: self.containerWidth, height: self.itemHeight)) + } + } + + private final class ScrollViewImpl: UIScrollView { + override func touchesShouldCancel(in view: UIView) -> Bool { + return true + } + } + + class View: UIView, UIScrollViewDelegate { + private let scrollView: ScrollViewImpl + + private let measureItem = ComponentView() + private var visibleItems: [String: ComponentView] = [:] + private var separatorViews: [String: UIView] = [:] + + private var ignoreScrolling: Bool = false + + private var component: StarsTransactionsListPanelComponent? + private var environment: StarsTransactionsPanelEnvironment? + private var itemLayout: ItemLayout? + + override init(frame: CGRect) { + self.scrollView = ScrollViewImpl() + + super.init(frame: frame) + + self.scrollView.delaysContentTouches = true + self.scrollView.canCancelContentTouches = true + self.scrollView.clipsToBounds = false + if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { + self.scrollView.contentInsetAdjustmentBehavior = .never + } + if #available(iOS 13.0, *) { + self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false + } + self.scrollView.showsVerticalScrollIndicator = true + self.scrollView.showsHorizontalScrollIndicator = false + self.scrollView.alwaysBounceHorizontal = false + self.scrollView.scrollsToTop = false + self.scrollView.delegate = self + self.scrollView.clipsToBounds = true + self.addSubview(self.scrollView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + if !self.ignoreScrolling { + self.updateScrolling(transition: .immediate) + } + } + + func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + cancelContextGestures(view: scrollView) + } + + private func updateScrolling(transition: Transition) { + guard let component = self.component, let environment = self.environment, let items = component.items, let itemLayout = self.itemLayout else { + return + } + + let visibleBounds = self.scrollView.bounds.insetBy(dx: 0.0, dy: -100.0) + + var validIds = Set() + if let visibleItems = itemLayout.visibleItems(for: visibleBounds) { + for index in visibleItems.lowerBound ..< visibleItems.upperBound { + if index >= items.items.count { + continue + } + let item = items.items[index] + let id = item.transaction.id + validIds.insert(id) + + var itemTransition = transition + let itemView: ComponentView + let separatorView: UIView + if let current = self.visibleItems[id], let currentSeparator = self.separatorViews[id] { + itemView = current + separatorView = currentSeparator + } else { + itemTransition = .immediate + itemView = ComponentView() + self.visibleItems[id] = itemView + + separatorView = UIView() + self.separatorViews[id] = separatorView + self.addSubview(separatorView) + } + + separatorView.backgroundColor = environment.theme.list.itemBlocksSeparatorColor + + let fontBaseDisplaySize = 17.0 + + let itemTitle: String + let itemSubtitle: String + let itemLabel: NSAttributedString + switch item.transaction.peer { + case let .peer(peer): + itemTitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast) + itemLabel = NSAttributedString(string: "- \(item.transaction.count * -1)", font: Font.medium(fontBaseDisplaySize), textColor: environment.theme.list.itemDestructiveColor) + case .appStore: + itemTitle = "In-App Purchase" + itemLabel = NSAttributedString(string: "+ \(item.transaction.count)", font: Font.medium(fontBaseDisplaySize), textColor: environment.theme.list.itemDisclosureActions.constructive.fillColor) + case .playMarket: + itemTitle = "Play Market" + itemLabel = NSAttributedString(string: "+ \(item.transaction.count)", font: Font.medium(fontBaseDisplaySize), textColor: environment.theme.list.itemDisclosureActions.constructive.fillColor) + case .fragment: + itemTitle = "Fragment" + itemLabel = NSAttributedString(string: "+ \(item.transaction.count)", font: Font.medium(fontBaseDisplaySize), textColor: environment.theme.list.itemDisclosureActions.constructive.fillColor) + } + itemSubtitle = stringForMediumCompactDate(timestamp: item.transaction.date, strings: environment.strings, dateTimeFormat: environment.dateTimeFormat) + + let _ = itemView.update( + transition: itemTransition, + component: AnyComponent(ListActionItemComponent( + theme: environment.theme, + title: AnyComponent(VStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: itemTitle, + font: Font.semibold(fontBaseDisplaySize), + textColor: environment.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 0 + ))), + AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: itemSubtitle, + font: Font.regular(floor(fontBaseDisplaySize * 14.0 / 17.0)), + textColor: environment.theme.list.itemSecondaryTextColor + )), + maximumNumberOfLines: 0, + lineSpacing: 0.18 + ))) + ], alignment: .left, spacing: 2.0)), + contentInsets: UIEdgeInsets(top: 11.0, left: 0.0, bottom: 11.0, right: 0.0), + leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(AvatarComponent(context: component.context, theme: environment.theme, peer: item.transaction.peer)))), + icon: nil, + accessory: .custom(ListActionItemComponent.CustomAccessory(component: AnyComponentWithIdentity(id: "label", component: AnyComponent(LabelComponent(text: itemLabel))), insets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16.0))), + action: { [weak self] _ in + guard let self, let component = self.component else { + return + } + component.action(item.transaction) + } + )), + environment: {}, + containerSize: CGSize(width: itemLayout.containerWidth, height: itemLayout.itemHeight) + ) + let itemFrame = itemLayout.itemFrame(for: index) + if let itemComponentView = itemView.view { + if itemComponentView.superview == nil { + self.scrollView.addSubview(itemComponentView) + } + itemTransition.setFrame(view: itemComponentView, frame: itemFrame) + } + let sideInset: CGFloat = 60.0 + itemTransition.setFrame(view: separatorView, frame: CGRect(x: sideInset, y: itemFrame.maxY, width: itemFrame.width - sideInset, height: UIScreenPixel)) + } + } + + var removeIds: [String] = [] + for (id, itemView) in self.visibleItems { + if !validIds.contains(id) { + removeIds.append(id) + if let itemComponentView = itemView.view { + transition.setAlpha(view: itemComponentView, alpha: 0.0, completion: { [weak itemComponentView] _ in + itemComponentView?.removeFromSuperview() + }) + } + } + } + for (id, separatorView) in self.separatorViews { + if !validIds.contains(id) { + transition.setAlpha(view: separatorView, alpha: 0.0, completion: { [weak separatorView] _ in + separatorView?.removeFromSuperview() + }) + } + } + for id in removeIds { + self.visibleItems.removeValue(forKey: id) + } + } + + func update(component: StarsTransactionsListPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.component = component + + let environment = environment[StarsTransactionsPanelEnvironment.self].value + self.environment = environment + + let fontBaseDisplaySize = 17.0 + let measureItemSize = self.measureItem.update( + transition: .immediate, + component: AnyComponent(ListActionItemComponent( + theme: environment.theme, + title: AnyComponent(VStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: "ABC", + font: Font.regular(fontBaseDisplaySize), + textColor: environment.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 0 + ))), + AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: "abc", + font: Font.regular(floor(fontBaseDisplaySize * 13.0 / 17.0)), + textColor: environment.theme.list.itemSecondaryTextColor + )), + maximumNumberOfLines: 0, + lineSpacing: 0.18 + ))) + ], alignment: .left, spacing: 2.0)), + leftIcon: nil, + icon: nil, + accessory: nil, + action: { _ in } + )), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: 1000.0) + ) + + let itemLayout = ItemLayout( + containerInsets: environment.containerInsets, + containerWidth: availableSize.width, + itemHeight: measureItemSize.height, + itemCount: component.items?.items.count ?? 0 + ) + self.itemLayout = itemLayout + + self.ignoreScrolling = true + let contentOffset = self.scrollView.bounds.minY + transition.setPosition(view: self.scrollView, position: CGRect(origin: CGPoint(), size: availableSize).center) + var scrollBounds = self.scrollView.bounds + scrollBounds.size = availableSize + if !environment.isScrollable { + scrollBounds.origin = CGPoint() + } + transition.setBounds(view: self.scrollView, bounds: scrollBounds) + self.scrollView.isScrollEnabled = environment.isScrollable + let contentSize = CGSize(width: availableSize.width, height: itemLayout.contentHeight) + if self.scrollView.contentSize != contentSize { + self.scrollView.contentSize = contentSize + } + self.scrollView.scrollIndicatorInsets = environment.containerInsets + if !transition.animation.isImmediate && self.scrollView.bounds.minY != contentOffset { + let deltaOffset = self.scrollView.bounds.minY - contentOffset + transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: -deltaOffset), to: CGPoint(), additive: true) + } + self.ignoreScrolling = false + self.updateScrolling(transition: transition) + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +func cancelContextGestures(view: UIView) { + if let gestureRecognizers = view.gestureRecognizers { + for gesture in gestureRecognizers { + if let gesture = gesture as? ContextGesture { + gesture.cancel() + } + } + } + for subview in view.subviews { + cancelContextGestures(view: subview) + } +} + +private final class AvatarComponent: Component { + let context: AccountContext + let theme: PresentationTheme + let peer: StarsContext.State.Transaction.Peer + + init(context: AccountContext, theme: PresentationTheme, peer: StarsContext.State.Transaction.Peer) { + self.context = context + self.theme = theme + self.peer = peer + } + + static func ==(lhs: AvatarComponent, rhs: AvatarComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.peer != rhs.peer { + return false + } + return true + } + + final class View: UIView { + private let avatarNode: AvatarNode + private let backgroundView = UIImageView() + private let iconView = UIImageView() + + private var component: AvatarComponent? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 16.0)) + + super.init(frame: frame) + + self.iconView.contentMode = .center + self.iconView.image = UIImage(bundleImageName: "Premium/Stars/TopUp") + + self.addSubnode(self.avatarNode) + self.addSubview(self.backgroundView) + self.addSubview(self.iconView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: AvatarComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.component = component + self.state = state + + let size = CGSize(width: 40.0, height: 40.0) + + let gradientImage = generateGradientFilledCircleImage(diameter: size.width, colors: [UIColor(rgb: 0xf67447).cgColor, UIColor(rgb: 0xfdbe1c).cgColor], direction: .mirroredDiagonal) + + switch component.peer { + case let .peer(peer): + self.avatarNode.setPeer( + context: component.context, + theme: component.theme, + peer: peer, + synchronousLoad: true + ) + self.backgroundView.isHidden = true + self.iconView.isHidden = true + self.avatarNode.isHidden = false + case .appStore: + self.backgroundView.image = gradientImage + self.backgroundView.isHidden = false + self.iconView.isHidden = false + self.avatarNode.isHidden = true + case .playMarket: + self.backgroundView.image = gradientImage + self.backgroundView.isHidden = false + self.iconView.isHidden = false + self.avatarNode.isHidden = true + case .fragment: + self.backgroundView.image = gradientImage + self.backgroundView.isHidden = false + self.iconView.isHidden = false + self.avatarNode.isHidden = true + } + + self.avatarNode.frame = CGRect(origin: .zero, size: size) + self.iconView.frame = CGRect(origin: .zero, size: size) + self.backgroundView.frame = CGRect(origin: .zero, size: size) + + return size + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class LabelComponent: CombinedComponent { + let text: NSAttributedString + + init( + text: NSAttributedString + ) { + self.text = text + } + + static func ==(lhs: LabelComponent, rhs: LabelComponent) -> Bool { + if lhs.text != rhs.text { + return false + } + return true + } + + static var body: Body { + let text = Child(MultilineTextComponent.self) + let icon = Child(BundleIconComponent.self) + + return { context in + let component = context.component + + let text = text.update( + component: MultilineTextComponent(text: .plain(component.text)), + availableSize: CGSize(width: 100.0, height: 40.0), + transition: context.transition + ) + + let iconSize = CGSize(width: 20.0, height: 20.0) + let icon = icon.update( + component: BundleIconComponent( + name: "Premium/Stars/Star", + tintColor: nil + ), + availableSize: iconSize, + transition: context.transition + ) + + let spacing: CGFloat = 3.0 + let totalWidth = text.size.width + spacing + iconSize.width + let size = CGSize(width: totalWidth, height: iconSize.height) + + context.add(text + .position(CGPoint(x: text.size.width / 2.0, y: size.height / 2.0)) + ) + context.add(icon + .position(CGPoint(x: totalWidth - iconSize.width / 2.0, y: size.height / 2.0)) + ) + return size + } + } +} diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsPanelContainerComponent.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsPanelContainerComponent.swift new file mode 100644 index 0000000000..e295255123 --- /dev/null +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsPanelContainerComponent.swift @@ -0,0 +1,795 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import ComponentDisplayAdapters +import TelegramPresentationData + +final class StarsTransactionsPanelContainerEnvironment: Equatable { + let isScrollable: Bool + + init( + isScrollable: Bool + ) { + self.isScrollable = isScrollable + } + + static func ==(lhs: StarsTransactionsPanelContainerEnvironment, rhs: StarsTransactionsPanelContainerEnvironment) -> Bool { + if lhs.isScrollable != rhs.isScrollable { + return false + } + return true + } +} + +final class StarsTransactionsPanelEnvironment: Equatable { + let theme: PresentationTheme + let strings: PresentationStrings + let dateTimeFormat: PresentationDateTimeFormat + let containerInsets: UIEdgeInsets + let isScrollable: Bool + + init( + theme: PresentationTheme, + strings: PresentationStrings, + dateTimeFormat: PresentationDateTimeFormat, + containerInsets: UIEdgeInsets, + isScrollable: Bool + ) { + self.theme = theme + self.strings = strings + self.dateTimeFormat = dateTimeFormat + self.containerInsets = containerInsets + self.isScrollable = isScrollable + } + + static func ==(lhs: StarsTransactionsPanelEnvironment, rhs: StarsTransactionsPanelEnvironment) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.dateTimeFormat != rhs.dateTimeFormat { + return false + } + if lhs.containerInsets != rhs.containerInsets { + return false + } + if lhs.isScrollable != rhs.isScrollable { + return false + } + return true + } +} + +private final class StarsTransactionsHeaderItemComponent: CombinedComponent { + let theme: PresentationTheme + let title: String + let activityFraction: CGFloat + + init( + theme: PresentationTheme, + title: String, + activityFraction: CGFloat + ) { + self.theme = theme + self.title = title + self.activityFraction = activityFraction + } + + static func ==(lhs: StarsTransactionsHeaderItemComponent, rhs: StarsTransactionsHeaderItemComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.title != rhs.title { + return false + } + if lhs.activityFraction != rhs.activityFraction { + return false + } + return true + } + + static var body: Body { + let activeText = Child(Text.self) + let inactiveText = Child(Text.self) + + return { context in + let activeText = activeText.update( + component: Text(text: context.component.title, font: Font.medium(14.0), color: context.component.theme.list.itemAccentColor), + availableSize: context.availableSize, + transition: .immediate + ) + let inactiveText = inactiveText.update( + component: Text(text: context.component.title, font: Font.medium(14.0), color: context.component.theme.list.itemSecondaryTextColor), + availableSize: context.availableSize, + transition: .immediate + ) + + context.add(activeText + .position(CGPoint(x: activeText.size.width * 0.5, y: activeText.size.height * 0.5)) + .opacity(context.component.activityFraction) + ) + context.add(inactiveText + .position(CGPoint(x: inactiveText.size.width * 0.5, y: inactiveText.size.height * 0.5)) + .opacity(1.0 - context.component.activityFraction) + ) + + return activeText.size + } + } +} + +private extension CGFloat { + func interpolate(with other: CGFloat, fraction: CGFloat) -> CGFloat { + let invT = 1.0 - fraction + let result = other * fraction + self * invT + return result + } +} + +private extension CGPoint { + func interpolate(with other: CGPoint, fraction: CGFloat) -> CGPoint { + return CGPoint(x: self.x.interpolate(with: other.x, fraction: fraction), y: self.y.interpolate(with: other.y, fraction: fraction)) + } +} + +private extension CGSize { + func interpolate(with other: CGSize, fraction: CGFloat) -> CGSize { + return CGSize(width: self.width.interpolate(with: other.width, fraction: fraction), height: self.height.interpolate(with: other.height, fraction: fraction)) + } +} + +private extension CGRect { + func interpolate(with other: CGRect, fraction: CGFloat) -> CGRect { + return CGRect(origin: self.origin.interpolate(with: other.origin, fraction: fraction), size: self.size.interpolate(with: other.size, fraction: fraction)) + } +} + +private final class StarsTransactionsHeaderComponent: Component { + struct Item: Equatable { + let id: AnyHashable + let title: String + + init( + id: AnyHashable, + title: String + ) { + self.id = id + self.title = title + } + } + + let theme: PresentationTheme + let items: [Item] + let activeIndex: Int + let transitionFraction: CGFloat + let switchToPanel: (AnyHashable) -> Void + + init( + theme: PresentationTheme, + items: [Item], + activeIndex: Int, + transitionFraction: CGFloat, + switchToPanel: @escaping (AnyHashable) -> Void + ) { + self.theme = theme + self.items = items + self.activeIndex = activeIndex + self.transitionFraction = transitionFraction + self.switchToPanel = switchToPanel + } + + static func ==(lhs: StarsTransactionsHeaderComponent, rhs: StarsTransactionsHeaderComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.items != rhs.items { + return false + } + if lhs.activeIndex != rhs.activeIndex { + return false + } + if lhs.transitionFraction != rhs.transitionFraction { + return false + } + return true + } + + class View: UIView { + private var component: StarsTransactionsHeaderComponent? + + private var visibleItems: [AnyHashable: ComponentView] = [:] + private let activeItemLayer: SimpleLayer + + override init(frame: CGRect) { + self.activeItemLayer = SimpleLayer() + self.activeItemLayer.cornerRadius = 2.0 + self.activeItemLayer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] + + super.init(frame: frame) + + self.layer.addSublayer(self.activeItemLayer) + + self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + let point = recognizer.location(in: self) + var closestId: (CGFloat, AnyHashable)? + if self.bounds.contains(point) { + for (id, item) in self.visibleItems { + if let itemView = item.view { + let distance: CGFloat = min(abs(point.x - itemView.frame.minX), abs(point.x - itemView.frame.maxX)) + if let closestIdValue = closestId { + if distance < closestIdValue.0 { + closestId = (distance, id) + } + } else { + closestId = (distance, id) + } + } + } + } + if let closestId = closestId, let component = self.component { + component.switchToPanel(closestId.1) + } + } + } + + func update(component: StarsTransactionsHeaderComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + let themeUpdated = self.component?.theme !== component.theme + + self.component = component + + var validIds = Set() + for i in 0 ..< component.items.count { + let item = component.items[i] + validIds.insert(item.id) + + let itemView: ComponentView + var itemTransition = transition + if let current = self.visibleItems[item.id] { + itemView = current + } else { + itemTransition = .immediate + itemView = ComponentView() + self.visibleItems[item.id] = itemView + } + + let activeIndex: CGFloat = CGFloat(component.activeIndex) - component.transitionFraction + let activityDistance: CGFloat = abs(activeIndex - CGFloat(i)) + + let activityFraction: CGFloat + if activityDistance < 1.0 { + activityFraction = 1.0 - activityDistance + } else { + activityFraction = 0.0 + } + + let itemSize = itemView.update( + transition: itemTransition, + component: AnyComponent(StarsTransactionsHeaderItemComponent( + theme: component.theme, + title: item.title, + activityFraction: activityFraction + )), + environment: {}, + containerSize: availableSize + ) + + let itemHorizontalSpace = availableSize.width / CGFloat(component.items.count) + let itemX: CGFloat + if component.items.count == 1 { + itemX = 37.0 + } else { + itemX = itemHorizontalSpace * CGFloat(i) + floor((itemHorizontalSpace - itemSize.width) / 2.0) + } + + let itemFrame = CGRect(origin: CGPoint(x: itemX, y: floor((availableSize.height - itemSize.height) / 2.0)), size: itemSize) + if let itemComponentView = itemView.view { + if itemComponentView.superview == nil { + self.addSubview(itemComponentView) + itemComponentView.isUserInteractionEnabled = false + } + itemTransition.setFrame(view: itemComponentView, frame: itemFrame) + } + } + + if component.activeIndex < component.items.count { + let activeView = self.visibleItems[component.items[component.activeIndex].id]?.view + let nextIndex: Int + if component.transitionFraction > 0.0 { + nextIndex = max(0, component.activeIndex - 1) + } else { + nextIndex = min(component.items.count - 1, component.activeIndex + 1) + } + let nextView = self.visibleItems[component.items[nextIndex].id]?.view + if let activeView = activeView, let nextView = nextView { + let mergedFrame = activeView.frame.interpolate(with: nextView.frame, fraction: abs(component.transitionFraction)) + transition.setFrame(layer: self.activeItemLayer, frame: CGRect(origin: CGPoint(x: mergedFrame.minX, y: availableSize.height - 3.0), size: CGSize(width: mergedFrame.width, height: 3.0))) + } + } + + if themeUpdated { + self.activeItemLayer.backgroundColor = component.theme.list.itemAccentColor.cgColor + } + + var removeIds: [AnyHashable] = [] + for (id, itemView) in self.visibleItems { + if !validIds.contains(id) { + removeIds.append(id) + if let itemComponentView = itemView.view { + itemComponentView.removeFromSuperview() + } + } + } + for id in removeIds { + self.visibleItems.removeValue(forKey: id) + } + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +final class StarsTransactionsPanelContainerComponent: Component { + typealias EnvironmentType = StarsTransactionsPanelContainerEnvironment + + struct Item: Equatable { + let id: AnyHashable + let title: String + let panel: AnyComponent + + init( + id: AnyHashable, + title: String, + panel: AnyComponent + ) { + self.id = id + self.title = title + self.panel = panel + } + } + + let theme: PresentationTheme + let strings: PresentationStrings + let dateTimeFormat: PresentationDateTimeFormat + let insets: UIEdgeInsets + let items: [Item] + let currentPanelUpdated: (AnyHashable, Transition) -> Void + + init( + theme: PresentationTheme, + strings: PresentationStrings, + dateTimeFormat: PresentationDateTimeFormat, + insets: UIEdgeInsets, + items: [Item], + currentPanelUpdated: @escaping (AnyHashable, Transition) -> Void + ) { + self.theme = theme + self.strings = strings + self.dateTimeFormat = dateTimeFormat + self.insets = insets + self.items = items + self.currentPanelUpdated = currentPanelUpdated + } + + static func ==(lhs: StarsTransactionsPanelContainerComponent, rhs: StarsTransactionsPanelContainerComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.dateTimeFormat != rhs.dateTimeFormat { + return false + } + if lhs.insets != rhs.insets { + return false + } + if lhs.items != rhs.items { + return false + } + return true + } + + class View: UIView, UIGestureRecognizerDelegate { + private let topPanelBackgroundView: UIView + private let topPanelMergedBackgroundView: UIView + private let topPanelSeparatorLayer: SimpleLayer + private let header = ComponentView() + + private var component: StarsTransactionsPanelContainerComponent? + private weak var state: EmptyComponentState? + + private let panelsBackgroundLayer: SimpleLayer + private var visiblePanels: [AnyHashable: ComponentView] = [:] + private var actualVisibleIds = Set() + private var currentId: AnyHashable? + private var transitionFraction: CGFloat = 0.0 + private var animatingTransition: Bool = false + + override init(frame: CGRect) { + self.topPanelBackgroundView = UIView() + + self.topPanelMergedBackgroundView = UIView() + self.topPanelMergedBackgroundView.alpha = 0.0 + + self.topPanelSeparatorLayer = SimpleLayer() + + self.panelsBackgroundLayer = SimpleLayer() + + super.init(frame: frame) + + self.layer.addSublayer(self.panelsBackgroundLayer) + self.addSubview(self.topPanelBackgroundView) + self.addSubview(self.topPanelMergedBackgroundView) + self.layer.addSublayer(self.topPanelSeparatorLayer) + + let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] point in + guard let self, let component = self.component, let currentId = self.currentId else { + return [] + } + guard let index = component.items.firstIndex(where: { $0.id == currentId }) else { + return [] + } + + /*if strongSelf.tabsContainerNode.bounds.contains(strongSelf.view.convert(point, to: strongSelf.tabsContainerNode.view)) { + return [] + }*/ + + if index == 0 { + return .left + } + return [.left, .right] + }) + panRecognizer.delegate = self + panRecognizer.delaysTouchesBegan = false + panRecognizer.cancelsTouchesInView = true + self.addGestureRecognizer(panRecognizer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + var currentPanelView: UIView? { + guard let currentId = self.currentId, let panel = self.visiblePanels[currentId] else { + return nil + } + return panel.view + } + + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return false + } + + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { + if let _ = otherGestureRecognizer as? InteractiveTransitionGestureRecognizer { + return false + } + if let _ = otherGestureRecognizer as? UIPanGestureRecognizer { + return true + } + return false + } + + @objc private func panGesture(_ recognizer: UIPanGestureRecognizer) { + switch recognizer.state { + case .began: + func cancelContextGestures(view: UIView) { + if let gestureRecognizers = view.gestureRecognizers { + for gesture in gestureRecognizers { + if let gesture = gesture as? ContextGesture { + gesture.cancel() + } + } + } + for subview in view.subviews { + cancelContextGestures(view: subview) + } + } + + cancelContextGestures(view: self) + + //self.animatingTransition = true + case .changed: + guard let component = self.component, let currentId = self.currentId else { + return + } + guard let index = component.items.firstIndex(where: { $0.id == currentId }) else { + return + } + + let translation = recognizer.translation(in: self) + var transitionFraction = translation.x / self.bounds.width + if index <= 0 { + transitionFraction = min(0.0, transitionFraction) + } + if index >= component.items.count - 1 { + transitionFraction = max(0.0, transitionFraction) + } + self.transitionFraction = transitionFraction + self.state?.updated(transition: .immediate) + case .cancelled, .ended: + guard let component = self.component, let currentId = self.currentId else { + return + } + guard let index = component.items.firstIndex(where: { $0.id == currentId }) else { + return + } + + let translation = recognizer.translation(in: self) + let velocity = recognizer.velocity(in: self) + var directionIsToRight: Bool? + if abs(velocity.x) > 10.0 { + directionIsToRight = velocity.x < 0.0 + } else { + if abs(translation.x) > self.bounds.width / 2.0 { + directionIsToRight = translation.x > self.bounds.width / 2.0 + } + } + if let directionIsToRight = directionIsToRight { + var updatedIndex = index + if directionIsToRight { + updatedIndex = min(updatedIndex + 1, component.items.count - 1) + } else { + updatedIndex = max(updatedIndex - 1, 0) + } + self.currentId = component.items[updatedIndex].id + } + self.transitionFraction = 0.0 + + let transition = Transition(animation: .curve(duration: 0.35, curve: .spring)) + if let currentId = self.currentId { + self.state?.updated(transition: transition) + component.currentPanelUpdated(currentId, transition) + } + + self.animatingTransition = false + //self.currentPaneUpdated?(false) + + //self.currentPaneStatusPromise.set(self.currentPane?.node.status ?? .single(nil)) + default: + break + } + } + + func updateNavigationMergeFactor(value: CGFloat, transition: Transition) { + transition.setAlpha(view: self.topPanelMergedBackgroundView, alpha: value) + transition.setAlpha(view: self.topPanelBackgroundView, alpha: 1.0 - value) + } + + func update(component: StarsTransactionsPanelContainerComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + let environment = environment[StarsTransactionsPanelContainerEnvironment.self].value + + let themeUpdated = self.component?.theme !== component.theme + + self.component = component + self.state = state + + if themeUpdated { + self.panelsBackgroundLayer.backgroundColor = component.theme.list.itemBlocksBackgroundColor.cgColor + self.topPanelSeparatorLayer.backgroundColor = component.theme.list.itemBlocksSeparatorColor.cgColor + self.topPanelBackgroundView.backgroundColor = component.theme.list.itemBlocksBackgroundColor + self.topPanelMergedBackgroundView.backgroundColor = component.theme.rootController.navigationBar.blurredBackgroundColor + } + + let topPanelCoverHeight: CGFloat = 10.0 + + let topPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: -topPanelCoverHeight), size: CGSize(width: availableSize.width, height: 44.0)) + transition.setFrame(view: self.topPanelBackgroundView, frame: topPanelFrame) + transition.setFrame(view: self.topPanelMergedBackgroundView, frame: topPanelFrame) + + transition.setFrame(layer: self.panelsBackgroundLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: availableSize.width, height: availableSize.height - topPanelFrame.maxY))) + + transition.setFrame(layer: self.topPanelSeparatorLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: availableSize.width, height: UIScreenPixel))) + + if let currentIdValue = self.currentId, !component.items.contains(where: { $0.id == currentIdValue }) { + self.currentId = nil + } + if self.currentId == nil { + self.currentId = component.items.first?.id + } + + var visibleIds = Set() + var currentIndex: Int? + if let currentId = self.currentId { + visibleIds.insert(currentId) + + if let index = component.items.firstIndex(where: { $0.id == currentId }) { + currentIndex = index + if index != 0 { + visibleIds.insert(component.items[index - 1].id) + } + if index != component.items.count - 1 { + visibleIds.insert(component.items[index + 1].id) + } + } + } + + let _ = self.header.update( + transition: transition, + component: AnyComponent(StarsTransactionsHeaderComponent( + theme: component.theme, + items: component.items.map { item -> StarsTransactionsHeaderComponent.Item in + return StarsTransactionsHeaderComponent.Item( + id: item.id, + title: item.title + ) + }, + activeIndex: currentIndex ?? 0, + transitionFraction: self.transitionFraction, + switchToPanel: { [weak self] id in + guard let self, let component = self.component else { + return + } + if component.items.contains(where: { $0.id == id }) { + self.currentId = id + let transition = Transition(animation: .curve(duration: 0.35, curve: .spring)) + self.state?.updated(transition: transition) + component.currentPanelUpdated(id, transition) + } + } + )), + environment: {}, + containerSize: topPanelFrame.size + ) + if let headerView = self.header.view { + if headerView.superview == nil { + self.addSubview(headerView) + } + transition.setFrame(view: headerView, frame: topPanelFrame) + } + + let childEnvironment = StarsTransactionsPanelEnvironment( + theme: component.theme, + strings: component.strings, + dateTimeFormat: component.dateTimeFormat, + containerInsets: UIEdgeInsets(top: 0.0, left: component.insets.left, bottom: component.insets.bottom, right: component.insets.right), + isScrollable: environment.isScrollable + ) + + let centralPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: availableSize.width, height: availableSize.height - topPanelFrame.maxY)) + + if self.animatingTransition { + visibleIds = visibleIds.filter({ self.visiblePanels[$0] != nil }) + } + + self.actualVisibleIds = visibleIds + + for (id, _) in self.visiblePanels { + visibleIds.insert(id) + } + + var validIds = Set() + if let currentIndex { + var anyAnchorOffset: CGFloat = 0.0 + for (id, panel) in self.visiblePanels { + guard let itemIndex = component.items.firstIndex(where: { $0.id == id }), let panelView = panel.view else { + continue + } + var itemFrame = centralPanelFrame.offsetBy(dx: self.transitionFraction * availableSize.width, dy: 0.0) + if itemIndex < currentIndex { + itemFrame.origin.x -= itemFrame.width + } else if itemIndex > currentIndex { + itemFrame.origin.x += itemFrame.width + } + + anyAnchorOffset = itemFrame.minX - panelView.frame.minX + + break + } + + for id in visibleIds { + guard let itemIndex = component.items.firstIndex(where: { $0.id == id }) else { + continue + } + let panelItem = component.items[itemIndex] + + var itemFrame = centralPanelFrame.offsetBy(dx: self.transitionFraction * availableSize.width, dy: 0.0) + if itemIndex < currentIndex { + itemFrame.origin.x -= itemFrame.width + } else if itemIndex > currentIndex { + itemFrame.origin.x += itemFrame.width + } + + validIds.insert(panelItem.id) + + let panel: ComponentView + var panelTransition = transition + var animateInIfNeeded = false + if let current = self.visiblePanels[panelItem.id] { + panel = current + + if let panelView = panel.view, !panelView.bounds.isEmpty { + var wasHidden = false + if abs(panelView.frame.minX - availableSize.width) < .ulpOfOne || abs(panelView.frame.maxX - 0.0) < .ulpOfOne { + wasHidden = true + } + var isHidden = false + if abs(itemFrame.minX - availableSize.width) < .ulpOfOne || abs(itemFrame.maxX - 0.0) < .ulpOfOne { + isHidden = true + } + if wasHidden && isHidden { + panelTransition = .immediate + } + } + } else { + panelTransition = .immediate + animateInIfNeeded = true + + panel = ComponentView() + self.visiblePanels[panelItem.id] = panel + } + let _ = panel.update( + transition: panelTransition, + component: panelItem.panel, + environment: { + childEnvironment + }, + containerSize: centralPanelFrame.size + ) + if let panelView = panel.view { + if panelView.superview == nil { + self.insertSubview(panelView, belowSubview: self.topPanelBackgroundView) + } + + panelTransition.setFrame(view: panelView, frame: itemFrame, completion: { [weak self] _ in + guard let self else { + return + } + if !self.actualVisibleIds.contains(id) { + if let panel = self.visiblePanels[id] { + self.visiblePanels.removeValue(forKey: id) + panel.view?.removeFromSuperview() + } + } + }) + if animateInIfNeeded && anyAnchorOffset != 0.0 { + transition.animatePosition(view: panelView, from: CGPoint(x: -anyAnchorOffset, y: 0.0), to: CGPoint(), additive: true) + } + } + } + } + + var removeIds: [AnyHashable] = [] + for (id, panel) in self.visiblePanels { + if !validIds.contains(id) { + removeIds.append(id) + if let panelView = panel.view { + panelView.removeFromSuperview() + } + } + } + for id in removeIds { + self.visiblePanels.removeValue(forKey: id) + } + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift new file mode 100644 index 0000000000..5009a08b8d --- /dev/null +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift @@ -0,0 +1,694 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import ComponentFlow +import SwiftSignalKit +import ViewControllerComponent +import ComponentDisplayAdapters +import TelegramPresentationData +import AccountContext +import TelegramCore +import Postbox +import MultilineTextComponent +import BalancedTextComponent +import Markdown +import PremiumStarComponent +import ListSectionComponent +import TextFormat + +final class StarsTransactionsScreenComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let starsContext: StarsContext + let buy: () -> Void + + init( + context: AccountContext, + starsContext: StarsContext, + buy: @escaping () -> Void + ) { + self.context = context + self.starsContext = starsContext + self.buy = buy + } + + static func ==(lhs: StarsTransactionsScreenComponent, rhs: StarsTransactionsScreenComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.starsContext !== rhs.starsContext { + return false + } + return true + } + + private final class ScrollViewImpl: UIScrollView { + override func touchesShouldCancel(in view: UIView) -> Bool { + return true + } + + override var contentOffset: CGPoint { + set(value) { + var value = value + if value.y > self.contentSize.height - self.bounds.height { + value.y = max(0.0, self.contentSize.height - self.bounds.height) + self.bounces = false + } else { + self.bounces = true + } + super.contentOffset = value + } get { + return super.contentOffset + } + } + } + + class View: UIView, UIScrollViewDelegate { + private let scrollView: ScrollViewImpl + + private var currentSelectedPanelId: AnyHashable? + + private let navigationBackgroundView: BlurredBackgroundView + private let navigationSeparatorLayer: SimpleLayer + private let navigationSeparatorLayerContainer: SimpleLayer + + private let headerView = ComponentView() + private let headerOffsetContainer: UIView + + private let scrollContainerView: UIView + + private let overscroll = ComponentView() + private let fade = ComponentView() + private let starView = ComponentView() + private let titleView = ComponentView() + private let descriptionView = ComponentView() + + private let balanceView = ComponentView() + + private let topBalanceView = ComponentView() + + private let panelContainer = ComponentView() + + private var component: StarsTransactionsScreenComponent? + private weak var state: EmptyComponentState? + private var navigationMetrics: (navigationHeight: CGFloat, statusBarHeight: CGFloat)? + private var controller: (() -> ViewController?)? + + private var enableVelocityTracking: Bool = false + private var previousVelocityM1: CGFloat = 0.0 + private var previousVelocity: CGFloat = 0.0 + + private var ignoreScrolling: Bool = false + + private var stateDisposable: Disposable? + private var starsState: StarsContext.State? + + override init(frame: CGRect) { + self.headerOffsetContainer = UIView() + self.headerOffsetContainer.isUserInteractionEnabled = false + + self.navigationBackgroundView = BlurredBackgroundView(color: nil, enableBlur: true) + self.navigationBackgroundView.alpha = 0.0 + + self.navigationSeparatorLayer = SimpleLayer() + self.navigationSeparatorLayer.opacity = 0.0 + self.navigationSeparatorLayerContainer = SimpleLayer() + self.navigationSeparatorLayerContainer.opacity = 0.0 + + self.scrollContainerView = UIView() + self.scrollView = ScrollViewImpl() + + super.init(frame: frame) + + self.scrollView.delaysContentTouches = true + self.scrollView.canCancelContentTouches = true + self.scrollView.clipsToBounds = false + if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { + self.scrollView.contentInsetAdjustmentBehavior = .never + } + if #available(iOS 13.0, *) { + self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false + } + self.scrollView.showsVerticalScrollIndicator = false + self.scrollView.showsHorizontalScrollIndicator = false + self.scrollView.alwaysBounceHorizontal = false + self.scrollView.scrollsToTop = false + self.scrollView.delegate = self + self.scrollView.clipsToBounds = true + self.addSubview(self.scrollView) + + self.scrollView.addSubview(self.scrollContainerView) + + self.addSubview(self.navigationBackgroundView) + + self.navigationSeparatorLayerContainer.addSublayer(self.navigationSeparatorLayer) + self.layer.addSublayer(self.navigationSeparatorLayerContainer) + + self.addSubview(self.headerOffsetContainer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.stateDisposable?.dispose() + } + + func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + self.enableVelocityTracking = true + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + if !self.ignoreScrolling { + if self.enableVelocityTracking { + self.previousVelocityM1 = self.previousVelocity + if let value = (scrollView.value(forKey: (["_", "verticalVelocity"] as [String]).joined()) as? NSNumber)?.doubleValue { + self.previousVelocity = CGFloat(value) + } + } + + self.updateScrolling(transition: .immediate) + } + } + + func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { + guard let _ = self.navigationMetrics else { + return + } + + let paneAreaExpansionDistance: CGFloat = 32.0 + let paneAreaExpansionFinalPoint: CGFloat = scrollView.contentSize.height - scrollView.bounds.height + if targetContentOffset.pointee.y > paneAreaExpansionFinalPoint - paneAreaExpansionDistance && targetContentOffset.pointee.y < paneAreaExpansionFinalPoint { + targetContentOffset.pointee.y = paneAreaExpansionFinalPoint + self.enableVelocityTracking = false + self.previousVelocity = 0.0 + self.previousVelocityM1 = 0.0 + } + } + + private func updateScrolling(transition: Transition) { + let scrollBounds = self.scrollView.bounds + + let isLockedAtPanels = scrollBounds.maxY == self.scrollView.contentSize.height + + if let navigationMetrics = self.navigationMetrics { + let topInset: CGFloat = navigationMetrics.navigationHeight - 56.0 + + let titleOffset: CGFloat + let titleScale: CGFloat + let titleOffsetDelta = (topInset + 160.0) - (navigationMetrics.statusBarHeight + (navigationMetrics.navigationHeight - navigationMetrics.statusBarHeight) / 2.0) + + var topContentOffset = self.scrollView.contentOffset.y + + let navigationBackgroundAlpha = min(20.0, max(0.0, topContentOffset - 95.0)) / 20.0 + topContentOffset = topContentOffset + max(0.0, min(1.0, topContentOffset / titleOffsetDelta)) * 10.0 + titleOffset = topContentOffset + let fraction = max(0.0, min(1.0, titleOffset / titleOffsetDelta)) + titleScale = 1.0 - fraction * 0.36 + + let headerTransition: Transition = .immediate + + if let starView = self.starView.view { + let starPosition = CGPoint(x: self.scrollView.frame.width / 2.0, y: topInset + starView.bounds.height / 2.0 - 30.0 - titleOffset * titleScale) + + headerTransition.setPosition(view: starView, position: starPosition) + headerTransition.setScale(view: starView, scale: titleScale) + } + + if let titleView = self.titleView.view { + let titlePosition = CGPoint(x: scrollBounds.width / 2.0, y: max(topInset + 160.0 - titleOffset, navigationMetrics.statusBarHeight + (navigationMetrics.navigationHeight - navigationMetrics.statusBarHeight) / 2.0)) + + headerTransition.setPosition(view: titleView, position: titlePosition) + headerTransition.setScale(view: titleView, scale: titleScale) + } + + let animatedTransition = Transition(animation: .curve(duration: 0.18, curve: .easeInOut)) + animatedTransition.setAlpha(view: self.navigationBackgroundView, alpha: navigationBackgroundAlpha) + animatedTransition.setAlpha(layer: self.navigationSeparatorLayerContainer, alpha: navigationBackgroundAlpha) + + let expansionDistance: CGFloat = 32.0 + var expansionDistanceFactor: CGFloat = abs(scrollBounds.maxY - self.scrollView.contentSize.height) / expansionDistance + expansionDistanceFactor = max(0.0, min(1.0, expansionDistanceFactor)) + + transition.setAlpha(layer: self.navigationSeparatorLayer, alpha: expansionDistanceFactor) + if let panelContainerView = self.panelContainer.view as? StarsTransactionsPanelContainerComponent.View { + panelContainerView.updateNavigationMergeFactor(value: 1.0 - expansionDistanceFactor, transition: transition) + } + + if let topBalanceView = self.topBalanceView.view { + topBalanceView.alpha = 1.0 - expansionDistanceFactor + } + } + + +// if let headerView = self.headerView.view, let navigationMetrics = self.navigationMetrics { +// var headerOffset: CGFloat = scrollBounds.minY +// +// let minY = navigationMetrics.statusBarHeight + floor((navigationMetrics.navigationHeight - navigationMetrics.statusBarHeight) / 2.0) +// +// let minOffset = headerView.center.y - minY +// +// headerOffset = min(headerOffset, minOffset) +// +// let animatedTransition = Transition(animation: .curve(duration: 0.18, curve: .easeInOut)) +// let navigationBackgroundAlpha: CGFloat = abs(headerOffset - minOffset) < 4.0 ? 1.0 : 0.0 +// +// animatedTransition.setAlpha(view: self.navigationBackgroundView, alpha: navigationBackgroundAlpha) +// animatedTransition.setAlpha(layer: self.navigationSeparatorLayerContainer, alpha: navigationBackgroundAlpha) +// +// let expansionDistance: CGFloat = 32.0 +// var expansionDistanceFactor: CGFloat = abs(scrollBounds.maxY - self.scrollView.contentSize.height) / expansionDistance +// expansionDistanceFactor = max(0.0, min(1.0, expansionDistanceFactor)) +// +// transition.setAlpha(layer: self.navigationSeparatorLayer, alpha: expansionDistanceFactor) +// if let panelContainerView = self.panelContainer.view as? StarsTransactionsPanelContainerComponent.View { +// panelContainerView.updateNavigationMergeFactor(value: 1.0 - expansionDistanceFactor, transition: transition) +// } +// +// var offsetFraction: CGFloat = abs(headerOffset - minOffset) / 60.0 +// offsetFraction = min(1.0, max(0.0, offsetFraction)) +// transition.setScale(view: headerView, scale: 1.0 * offsetFraction + 0.8 * (1.0 - offsetFraction)) +// +// transition.setBounds(view: self.headerOffsetContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: headerOffset), size: self.headerOffsetContainer.bounds.size)) +// } + + let _ = self.panelContainer.updateEnvironment( + transition: transition, + environment: { + StarsTransactionsPanelContainerEnvironment(isScrollable: isLockedAtPanels) + } + ) + } + + private var previousBalance: Int64? + + private var isUpdating = false + func update(component: StarsTransactionsScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + + self.component = component + self.state = state + + var balanceUpdated = false + if let starsState = self.starsState { + if let previousBalance, starsState.balance != previousBalance { + balanceUpdated = true + } + self.previousBalance = starsState.balance + } + + let environment = environment[ViewControllerComponentContainer.Environment.self].value + + if self.stateDisposable == nil { + self.stateDisposable = (component.starsContext.state + |> deliverOnMainQueue).start(next: { [weak self] state in + guard let self else { + return + } + self.starsState = state + if !self.isUpdating { + self.state?.updated() + } + }) + } + + var wasLockedAtPanels = false + if let panelContainerView = self.panelContainer.view, let navigationMetrics = self.navigationMetrics { + if self.scrollView.bounds.minY > 0.0 && abs(self.scrollView.bounds.minY - (panelContainerView.frame.minY - navigationMetrics.navigationHeight)) <= UIScreenPixel { + wasLockedAtPanels = true + } + } + + self.controller = environment.controller + + self.navigationMetrics = (environment.navigationHeight, environment.statusBarHeight) + + self.navigationSeparatorLayer.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor + + let navigationFrame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: environment.navigationHeight)) + self.navigationBackgroundView.updateColor(color: environment.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) + self.navigationBackgroundView.update(size: navigationFrame.size, transition: transition.containedViewLayoutTransition) + transition.setFrame(view: self.navigationBackgroundView, frame: navigationFrame) + + let navigationSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationFrame.maxY), size: CGSize(width: availableSize.width, height: UIScreenPixel)) + + transition.setFrame(layer: self.navigationSeparatorLayerContainer, frame: navigationSeparatorFrame) + transition.setFrame(layer: self.navigationSeparatorLayer, frame: CGRect(origin: CGPoint(), size: navigationSeparatorFrame.size)) + + self.backgroundColor = environment.theme.list.blocksBackgroundColor + + var contentHeight: CGFloat = 0.0 + + let sideInsets: CGFloat = environment.safeInsets.left + environment.safeInsets.right + 16 * 2.0 + let bottomInset: CGFloat = environment.safeInsets.bottom + + contentHeight += environment.statusBarHeight + + let starTransition: Transition = .immediate + + var topBackgroundColor = environment.theme.list.plainBackgroundColor + let bottomBackgroundColor = environment.theme.list.blocksBackgroundColor + if environment.theme.overallDarkAppearance { + topBackgroundColor = bottomBackgroundColor + } + + let overscrollSize = self.overscroll.update( + transition: .immediate, + component: AnyComponent(Rectangle(color: topBackgroundColor)), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: 1000.0) + ) + let overscrollFrame = CGRect(origin: CGPoint(x: 0.0, y: -overscrollSize.height), size: overscrollSize) + if let overscrollView = self.overscroll.view { + if overscrollView.superview == nil { + self.scrollView.addSubview(overscrollView) + } + starTransition.setFrame(view: overscrollView, frame: overscrollFrame) + } + + let fadeSize = self.fade.update( + transition: .immediate, + component: AnyComponent(RoundedRectangle( + colors: [ + topBackgroundColor, + bottomBackgroundColor + ], + cornerRadius: 0.0, + gradientDirection: .vertical + )), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: 1000.0) + ) + let fadeFrame = CGRect(origin: CGPoint(x: 0.0, y: -fadeSize.height), size: fadeSize) + if let fadeView = self.fade.view { + if fadeView.superview == nil { + self.scrollView.addSubview(fadeView) + } + starTransition.setFrame(view: fadeView, frame: fadeFrame) + } + + let starSize = self.starView.update( + transition: .immediate, + component: AnyComponent(PremiumStarComponent( + isIntro: true, + isVisible: true, + hasIdleAnimations: true, + colors: [ + UIColor(rgb: 0xea8904), + UIColor(rgb: 0xf09903), + UIColor(rgb: 0xfec209), + UIColor(rgb: 0xfed31a) + ] + )), + environment: {}, + containerSize: CGSize(width: min(414.0, availableSize.width), height: 220.0) + ) + let starFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: starSize) + if let starView = self.starView.view { + if starView.superview == nil { + self.insertSubview(starView, aboveSubview: self.scrollView) + } + starTransition.setFrame(view: starView, frame: starFrame) + } + + let titleSize = self.titleView.update( + transition: .immediate, + component: AnyComponent( + MultilineTextComponent( + text: .plain(NSAttributedString(string: "Telegram Stars", font: Font.bold(28.0), textColor: environment.theme.list.itemPrimaryTextColor)), + horizontalAlignment: .center, + truncationType: .end, + maximumNumberOfLines: 1 + ) + ), + environment: {}, + containerSize: availableSize + ) + if let titleView = self.titleView.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + starTransition.setBounds(view: titleView, bounds: CGRect(origin: .zero, size: titleSize)) + } + + let textFont = Font.regular(14.0) + let boldTextFont = Font.semibold(14.0) + let textColor = environment.theme.actionSheet.primaryTextColor + let linkColor = environment.theme.actionSheet.controlAccentColor + let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in + return (TelegramTextAttributes.URL, contents) + }) + let balanceAttributedString = parseMarkdownIntoAttributedString(" Balance\n > **\(starsState?.balance ?? 0)**", attributes: markdownAttributes, textAlignment: .right).mutableCopy() as! NSMutableAttributedString + if let range = balanceAttributedString.string.range(of: ">"), let chevronImage = generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: UIColor(rgb: 0xf09903)) { + balanceAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: balanceAttributedString.string)) + balanceAttributedString.addAttribute(.foregroundColor, value: UIColor(rgb: 0xf09903), range: NSRange(range, in: balanceAttributedString.string)) + balanceAttributedString.addAttribute(.baselineOffset, value: 2.0, range: NSRange(range, in: balanceAttributedString.string)) + } + let topBalanceSize = self.topBalanceView.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(balanceAttributedString), + horizontalAlignment: .right, + maximumNumberOfLines: 0, + lineSpacing: 0.1 + )), + environment: {}, + containerSize: CGSize(width: 120.0, height: 100.0) + ) + if let topBalanceView = self.topBalanceView.view { + if topBalanceView.superview == nil { + topBalanceView.alpha = 0.0 + self.addSubview(topBalanceView) + } + starTransition.setFrame(view: topBalanceView, frame: CGRect(origin: CGPoint(x: availableSize.width - topBalanceSize.width - 16.0, y: 56.0), size: topBalanceSize)) + } + + contentHeight += 181.0 + + let descriptionSize = self.descriptionView.update( + transition: .immediate, + component: AnyComponent( + BalancedTextComponent( + text: .plain(NSAttributedString(string: "Buy Stars to unlock content and services in miniapps on Telegram.", font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor)), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.2 + ) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInsets - 8.0, height: 240.0) + ) + let descriptionFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - descriptionSize.width) / 2.0), y: contentHeight), size: descriptionSize) + if let descriptionView = self.descriptionView.view { + if descriptionView.superview == nil { + self.scrollView.addSubview(descriptionView) + } + + starTransition.setFrame(view: descriptionView, frame: descriptionFrame) + } + + contentHeight += descriptionSize.height + contentHeight += 29.0 + + let balanceSize = self.balanceView.update( + transition: .immediate, + component: AnyComponent(ListSectionComponent( + theme: environment.theme, + header: nil, + footer: nil, + items: [AnyComponentWithIdentity(id: 0, component: AnyComponent( + StarsBalanceComponent( + theme: environment.theme, + strings: environment.strings, + count: self.starsState?.balance ?? 0, + buy: { [weak self] in + guard let self, let component = self.component else { + return + } + component.buy() + } + ) + ))] + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInsets, height: availableSize.height) + ) + let balanceFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - balanceSize.width) / 2.0), y: contentHeight), size: balanceSize) + if let balanceView = self.balanceView.view { + if balanceView.superview == nil { + self.scrollView.addSubview(balanceView) + } + starTransition.setFrame(view: balanceView, frame: balanceFrame) + } + + contentHeight += balanceSize.height + contentHeight += 44.0 + + //TODO: localize + let transactions = self.starsState?.transactions ?? [] + let allItems = StarsTransactionsListPanelComponent.Items( + items: transactions.map { StarsTransactionsListPanelComponent.Item(transaction: $0) } + ) + let incomingItems = StarsTransactionsListPanelComponent.Items( + items: transactions.filter { $0.count > 0 }.map { StarsTransactionsListPanelComponent.Item(transaction: $0) } + ) + let outgoingItems = StarsTransactionsListPanelComponent.Items( + items: transactions.filter { $0.count < 0 }.map { StarsTransactionsListPanelComponent.Item(transaction: $0) } + ) + + var panelItems: [StarsTransactionsPanelContainerComponent.Item] = [] + panelItems.append(StarsTransactionsPanelContainerComponent.Item( + id: "all", + title: "All Transactions", + panel: AnyComponent(StarsTransactionsListPanelComponent( + context: component.context, + items: allItems, + action: { _ in + } + )) + )) + + panelItems.append(StarsTransactionsPanelContainerComponent.Item( + id: "incoming", + title: "Incoming", + panel: AnyComponent(StarsTransactionsListPanelComponent( + context: component.context, + items: incomingItems, + action: { _ in + } + )) + )) + + panelItems.append(StarsTransactionsPanelContainerComponent.Item( + id: "outgoing", + title: "Outgoing", + panel: AnyComponent(StarsTransactionsListPanelComponent( + context: component.context, + items: outgoingItems, + action: { _ in + } + )) + )) + + var panelTransition = transition + if balanceUpdated { + panelTransition = .easeInOut(duration: 0.25) + } + + if !panelItems.isEmpty { + let panelContainerSize = self.panelContainer.update( + transition: panelTransition, + component: AnyComponent(StarsTransactionsPanelContainerComponent( + theme: environment.theme, + strings: environment.strings, + dateTimeFormat: environment.dateTimeFormat, + insets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left, bottom: bottomInset, right: environment.safeInsets.right), + items: panelItems, + currentPanelUpdated: { [weak self] id, transition in + guard let self else { + return + } + self.currentSelectedPanelId = id + self.state?.updated(transition: transition) + } + )), + environment: { + StarsTransactionsPanelContainerEnvironment(isScrollable: wasLockedAtPanels) + }, + containerSize: CGSize(width: availableSize.width, height: availableSize.height - environment.navigationHeight) + ) + if let panelContainerView = self.panelContainer.view { + if panelContainerView.superview == nil { + self.scrollContainerView.addSubview(panelContainerView) + } + transition.setFrame(view: panelContainerView, frame: CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: panelContainerSize)) + } + contentHeight += panelContainerSize.height + } else { + self.panelContainer.view?.removeFromSuperview() + } + + self.ignoreScrolling = true + + let contentOffset = self.scrollView.bounds.minY + transition.setPosition(view: self.scrollView, position: CGRect(origin: CGPoint(), size: availableSize).center) + let contentSize = CGSize(width: availableSize.width, height: contentHeight) + if self.scrollView.contentSize != contentSize { + self.scrollView.contentSize = contentSize + } + transition.setFrame(view: self.scrollContainerView, frame: CGRect(origin: CGPoint(), size: contentSize)) + + var scrollViewBounds = self.scrollView.bounds + scrollViewBounds.size = availableSize + if wasLockedAtPanels, let panelContainerView = self.panelContainer.view { + scrollViewBounds.origin.y = panelContainerView.frame.minY - environment.navigationHeight + } + transition.setBounds(view: self.scrollView, bounds: scrollViewBounds) + + if !wasLockedAtPanels && !transition.animation.isImmediate && self.scrollView.bounds.minY != contentOffset { + let deltaOffset = self.scrollView.bounds.minY - contentOffset + transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: -deltaOffset), to: CGPoint(), additive: true) + } + + self.ignoreScrolling = false + + self.updateScrolling(transition: transition) + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +public final class StarsTransactionsScreen: ViewControllerComponentContainer { + private let context: AccountContext + + private let options = Promise<[StarsTopUpOption]>() + + public init(context: AccountContext, starsContext: StarsContext, forceDark: Bool = false) { + self.context = context + + var buyImpl: (() -> Void)? + super.init(context: context, component: StarsTransactionsScreenComponent(context: context, starsContext: starsContext, buy: { + buyImpl?() + }), navigationBarAppearance: .transparent) + + self.options.set(.single([]) |> then(context.engine.payments.starsTopUpOptions())) + + buyImpl = { [weak self] in + guard let self else { + return + } + let _ = (self.options.get() + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] options in + guard let self else { + return + } + let controller = context.sharedContext.makeStarsPurchaseScreen(context: context, starsContext: starsContext, options: options, peerId: nil, requiredStars: nil) + self.push(controller) + }) + } + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public func viewDidLoad() { + super.viewDidLoad() + } +} diff --git a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/BUILD b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/BUILD new file mode 100644 index 0000000000..87f4b01dd8 --- /dev/null +++ b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/BUILD @@ -0,0 +1,39 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "StarsTransferScreen", + module_name = "StarsTransferScreen", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/ComponentFlow", + "//submodules/Components/ViewControllerComponent", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/Components/MultilineTextComponent", + "//submodules/Components/BalancedTextComponent", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/AppBundle", + "//submodules/ItemListUI", + "//submodules/TelegramStringFormatting", + "//submodules/PresentationDataUtils", + "//submodules/Components/SheetComponent", + "//submodules/UndoUI", + "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/TelegramUI/Components/ListSectionComponent", + "//submodules/TelegramUI/Components/ListActionItemComponent", + "//submodules/TelegramUI/Components/Premium/PremiumStarComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift new file mode 100644 index 0000000000..6981b5290e --- /dev/null +++ b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift @@ -0,0 +1,450 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import SwiftSignalKit +import TelegramCore +import Markdown +import TextFormat +import TelegramPresentationData +import ViewControllerComponent +import SheetComponent +import BalancedTextComponent +import MultilineTextComponent +import ItemListUI +import UndoUI +import AccountContext +import PremiumStarComponent +import ButtonComponent + +private final class SheetContent: CombinedComponent { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let invoice: TelegramMediaInvoice + let source: BotPaymentInvoiceSource + let inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError> + + init( + context: AccountContext, + invoice: TelegramMediaInvoice, + source: BotPaymentInvoiceSource, + inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError> + ) { + self.context = context + self.invoice = invoice + self.source = source + self.inputData = inputData + } + + static func ==(lhs: SheetContent, rhs: SheetContent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.invoice != rhs.invoice { + return false + } + return true + } + + final class State: ComponentState { + var cachedCloseImage: (UIImage, PresentationTheme)? + var cachedChevronImage: (UIImage, PresentationTheme)? + var cachedStarImage: (UIImage, PresentationTheme)? + + private let context: AccountContext + private let source: BotPaymentInvoiceSource + + var peer: EnginePeer? + var peerDisposable: Disposable? + var balance: Int64? + var form: BotPaymentForm? + + var inProgress = false + + init( + context: AccountContext, + source: BotPaymentInvoiceSource, + inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError> + ) { + self.context = context + self.source = source + + super.init() + + self.peerDisposable = (inputData + |> deliverOnMainQueue).start(next: { [weak self] inputData in + guard let self else { + return + } + self.balance = inputData?.0.balance ?? 0 + self.form = inputData?.1 + self.peer = inputData?.2 + self.updated(transition: .immediate) + }) + } + + deinit { + self.peerDisposable?.dispose() + } + + func buy(completion: @escaping () -> Void) { + guard let form else { + return + } + self.inProgress = true + self.updated() + + let _ = (self.context.engine.payments.sendStarsPaymentForm(formId: form.id, source: self.source) + |> deliverOnMainQueue).start(next: { _ in + completion() + }) + } + } + + func makeState() -> State { + return State(context: self.context, source: self.source, inputData: self.inputData) + } + + static var body: Body { + let background = Child(RoundedRectangle.self) + let star = Child(GiftAvatarComponent.self) + let closeButton = Child(Button.self) + let title = Child(Text.self) + let text = Child(BalancedTextComponent.self) + let balanceText = Child(MultilineTextComponent.self) + let button = Child(ButtonComponent.self) + + return { context in + let environment = context.environment[EnvironmentType.self] + let component = context.component + let state = context.state + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let theme = presentationData.theme +// let strings = presentationData.strings + +// let sideInset: CGFloat = 16.0 + environment.safeInsets.left + + var contentSize = CGSize(width: context.availableSize.width, height: 18.0) + + let background = background.update( + component: RoundedRectangle(color: theme.list.blocksBackgroundColor, cornerRadius: 8.0), + availableSize: CGSize(width: context.availableSize.width, height: 1000.0), + transition: .immediate + ) + context.add(background + .position(CGPoint(x: context.availableSize.width / 2.0, y: background.size.height / 2.0)) + ) + + if let peer = state.peer { + let star = star.update( + component: GiftAvatarComponent( + context: context.component.context, + theme: environment.theme, + peers: [peer], + isVisible: true, + hasIdleAnimations: true, + hasScaleAnimation: false, + color: UIColor(rgb: 0xf7ab04), + offset: 40.0 + ), + availableSize: CGSize(width: min(414.0, context.availableSize.width), height: 220.0), + transition: context.transition + ) + + context.add(star + .position(CGPoint(x: context.availableSize.width / 2.0, y: 0.0 + star.size.height / 2.0 - 30.0)) + ) + } + + let closeImage: UIImage + if let (image, cacheTheme) = state.cachedCloseImage, theme === cacheTheme { + closeImage = image + } else { + closeImage = generateCloseButtonImage(backgroundColor: UIColor(rgb: 0x808084, alpha: 0.1), foregroundColor: theme.actionSheet.inputClearButtonColor)! + state.cachedCloseImage = (closeImage, theme) + } + let closeButton = closeButton.update( + component: Button( + content: AnyComponent(Image(image: closeImage)), + action: { +// component.dismiss() + } + ), + availableSize: CGSize(width: 30.0, height: 30.0), + transition: .immediate + ) + context.add(closeButton + .position(CGPoint(x: context.availableSize.width - closeButton.size.width, y: 28.0)) + ) + + let constrainedTitleWidth = context.availableSize.width - 16.0 * 2.0 + + + contentSize.height += 130.0 + let title = title.update( + component: Text(text: "Confirm Your Purchase", font: Font.bold(24.0), color: theme.list.itemPrimaryTextColor), + availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height), + transition: .immediate + ) + context.add(title + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0)) + ) + contentSize.height += title.size.height + contentSize.height += 13.0 + + let textFont = Font.regular(15.0) + let boldTextFont = Font.semibold(15.0) + let textColor = theme.actionSheet.primaryTextColor + let linkColor = theme.actionSheet.controlAccentColor + let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in + return (TelegramTextAttributes.URL, contents) + }) + + let amount = component.invoice.totalAmount + let text = text.update( + component: BalancedTextComponent( + text: .markdown(text: "Do you want to buy **\(component.invoice.title)** in **\(state.peer?.compactDisplayTitle ?? "levlam_bot")** for **\(amount) Stars**?", attributes: markdownAttributes), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.2 + ), + availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height), + transition: .immediate + ) + context.add(text + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + text.size.height / 2.0)) + ) + contentSize.height += text.size.height + contentSize.height += 24.0 + + if state.cachedChevronImage == nil || state.cachedChevronImage?.1 !== theme { + state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: UIColor(rgb: 0xf09903))!, theme) + } + + let balanceAttributedString = parseMarkdownIntoAttributedString("Balance\n > **\(state.balance ?? 0)**", attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString + if let range = balanceAttributedString.string.range(of: ">"), let chevronImage = state.cachedChevronImage?.0 { + balanceAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: balanceAttributedString.string)) + balanceAttributedString.addAttribute(.foregroundColor, value: UIColor(rgb: 0xf09903), range: NSRange(range, in: balanceAttributedString.string)) + balanceAttributedString.addAttribute(.baselineOffset, value: 2.0, range: NSRange(range, in: balanceAttributedString.string)) + } + let balanceText = balanceText.update( + component: MultilineTextComponent( + text: .plain(balanceAttributedString), + horizontalAlignment: .left, + maximumNumberOfLines: 0, + lineSpacing: 0.2 + ), + availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height), + transition: .immediate + ) + context.add(balanceText + .position(CGPoint(x: 16.0 + balanceText.size.width / 2.0, y: 29.0)) + ) + + if state.cachedStarImage == nil || state.cachedStarImage?.1 !== theme { + state.cachedStarImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: .white)!, theme) + } + + let buttonAttributedString = NSMutableAttributedString(string: "Confirm and Pay > \(amount)", font: Font.semibold(17.0), textColor: .white, paragraphAlignment: .center) + if let range = buttonAttributedString.string.range(of: ">"), let starImage = state.cachedStarImage?.0 { + buttonAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: buttonAttributedString.string)) + buttonAttributedString.addAttribute(.foregroundColor, value: UIColor(rgb: 0xffffff), range: NSRange(range, in: buttonAttributedString.string)) + buttonAttributedString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: buttonAttributedString.string)) + } + + let controller = environment.controller() as? StarsTransferScreen + + let accountContext = component.context + let botTitle = state.peer?.compactDisplayTitle ?? "" + + let invoice = component.invoice + let button = button.update( + component: ButtonComponent( + background: ButtonComponent.Background( + color: theme.list.itemCheckColors.fillColor, + foreground: theme.list.itemCheckColors.foregroundColor, + pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), + cornerRadius: 10.0 + ), + content: AnyComponentWithIdentity( + id: AnyHashable(0), + component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString))) + ), + isEnabled: true, + displaysProgress: state.inProgress, + action: { [weak state, weak controller] in + state?.buy(completion: { [weak controller] in + let presentationData = accountContext.sharedContext.currentPresentationData.with { $0 } + let resultController = UndoOverlayController( + presentationData: presentationData, + content: .image(image: UIImage(bundleImageName: "Premium/Stars/Star")!, title: "Purchase Completed", text: "You acquired **\(invoice.title)** in **\(botTitle)** for **\(invoice.totalAmount) Stars**.", round: false, undoText: nil), + elevatedLayout: true, + action: { _ in return true}) + controller?.present(resultController, in: .window(.root)) + + controller?.dismissAnimated() + }) + } + ), + availableSize: CGSize(width: 361.0, height: 50), + transition: .immediate + ) + context.add(button + .clipsToBounds(true) + .cornerRadius(10.0) + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + button.size.height / 2.0)) + ) + contentSize.height += button.size.height + contentSize.height += 48.0 + + return contentSize + } + } +} + +private final class StarsTransferSheetComponent: CombinedComponent { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + private let context: AccountContext + private let invoice: TelegramMediaInvoice + private let source: BotPaymentInvoiceSource + private let inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError> + + init( + context: AccountContext, + invoice: TelegramMediaInvoice, + source: BotPaymentInvoiceSource, + inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError> + ) { + self.context = context + self.invoice = invoice + self.source = source + self.inputData = inputData + } + + static func ==(lhs: StarsTransferSheetComponent, rhs: StarsTransferSheetComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.invoice != rhs.invoice { + return false + } + return true + } + + static var body: Body { + let sheet = Child(SheetComponent<(EnvironmentType)>.self) + let animateOut = StoredActionSlot(Action.self) + + return { context in + let environment = context.environment[EnvironmentType.self] + + let controller = environment.controller + + let sheet = sheet.update( + component: SheetComponent( + content: AnyComponent(SheetContent( + context: context.component.context, + invoice: context.component.invoice, + source: context.component.source, + inputData: context.component.inputData + )), + backgroundColor: .blur(.light), + followContentSizeChanges: true, + animateOut: animateOut + ), + environment: { + environment + SheetComponentEnvironment( + isDisplaying: environment.value.isVisible, + isCentered: environment.metrics.widthClass == .regular, + hasInputHeight: !environment.inputHeight.isZero, + regularMetricsSize: CGSize(width: 430.0, height: 900.0), + dismiss: { animated in + if animated { + animateOut.invoke(Action { _ in + if let controller = controller() { + controller.dismiss(completion: nil) + } + }) + } else { + if let controller = controller() { + controller.dismiss(completion: nil) + } + } + } + ) + }, + availableSize: context.availableSize, + transition: context.transition + ) + + context.add(sheet + .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) + ) + + return context.availableSize + } + } +} + +public final class StarsTransferScreen: ViewControllerComponentContainer { + private let context: AccountContext + + public init( + context: AccountContext, + invoice: TelegramMediaInvoice, + source: BotPaymentInvoiceSource, + inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError> + ) { + self.context = context + + super.init( + context: context, + component: StarsTransferSheetComponent( + context: context, + invoice: invoice, + source: source, + inputData: inputData + ), + navigationBarAppearance: .none, + statusBarStyle: .ignore, + theme: .default + ) + + self.navigationPresentation = .flatModal + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func dismissAnimated() { + if let view = self.node.hostView.findTaggedView(tag: SheetComponent.View.Tag()) as? SheetComponent.View { + view.dismissAnimated() + } + } +} + +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() + }) +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Mock.imageset/mock.png b/submodules/TelegramUI/Images.xcassets/Premium/Mock.imageset/mock.png deleted file mode 100644 index b06e45609dafb4917e25aa0df76e3ded85236337..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8299 zcmch+XFS}^7dE~Tl86u@2%)ZyO{nZ6z3Z zBsjLF!XI_sk5L(+QVLdw&?Cb;Q%rWBq5w-Gw!?WFab~_7!QC!i+RD1_r2hLeQ7Q#K`tTTy$YE7L`sA<9-ci zA&vt8P!2Nz8Z}W|W+rT)`TLV09r9-Chm|9i8$D?|P7Gr(oYl|S4$&*p!l}`N_G>M2 z+(XgbQ0rYRrreF-K*Dg2G4=Y$Yj|$6K~+L{Q;X_m-PiiH%&v~n8$P7uUW1B1C8Av& z#CQ-H7*0H*=X2(z^FAa;Z#F0nIwl$1EE_UUrk@1H+nM&?678BDw%3)-#Bkyu1{nN> z=sU(GNQX;o5SDS@_F;MbsaZNBt3qM1&0J7VfTlCe(m5W0X8#OuZqqw`S3bn&VN;V| zPHthG8DWd^z$1`ZRqtF%{^ylL`|XbDjkzYEfKjosU9_3yH(8*r!)ktiXWJ842b54% zw07z8x$ypOpA@D;APxvSAm~1nkiN{i(37gB#Qi!RUmR%0Dt!-fA?Jaq&lIaZ9KkqN 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 N>dFwM62)h4{x1ge-zNY7 diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Contents.json new file mode 100644 index 0000000000..6e965652df --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/Star.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Star.imageset/Contents.json new file mode 100644 index 0000000000..d4ec011d84 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Star.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "transactionstar_20 (2).pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/Star.imageset/transactionstar_20 (2).pdf b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Star.imageset/transactionstar_20 (2).pdf new file mode 100644 index 0000000000000000000000000000000000000000..aefce12699347477249d8e934a8715fc2576b1d0 GIT binary patch literal 12891 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 bDE#Vg`F$h?yR{!6Fcc0W=jJ|pQT~4bqBtVv literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Mock2.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/Contents.json similarity index 75% rename from submodules/TelegramUI/Images.xcassets/Premium/Mock2.imageset/Contents.json rename to submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/Contents.json index 5f6829d46c..0b71e559ab 100644 --- a/submodules/TelegramUI/Images.xcassets/Premium/Mock2.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Mock2.png", + "filename" : "balancestar_48.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/balancestar_48.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/balancestar_48.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f55d110a3f9da4a6006b4d64cb1d02271266c410 GIT binary patch literal 14735 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 `catch` { _ -> Signal in return .single(nil) }) - strongSelf.present(BotCheckoutController(context: strongSelf.context, invoice: invoice, source: .message(messageId), inputData: inputData, completed: { currencyValue, receiptMessageId in - guard let strongSelf = self else { - return + if invoice.currency == "XTR" { + let statePromise = Promise() + statePromise.set(strongSelf.context.engine.payments.peerStarsState(peerId: strongSelf.context.account.peerId)) + let starsInputData = combineLatest( + inputData.get(), + statePromise.get() + ) + |> map { data, state -> (StarsContext.State, BotPaymentForm, EnginePeer?)? in + if let data, let state { + return (state, data.form, data.botPeer) + } else { + return nil + } } - strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .paymentSent(currencyValue: currencyValue, itemTitle: invoice.title), elevatedLayout: false, action: { action in - guard let strongSelf = self, let receiptMessageId = receiptMessageId else { + let _ = (starsInputData |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in + guard let strongSelf = self else { + return + } + let controller = strongSelf.context.sharedContext.makeStarsTransferScreen(context: strongSelf.context, invoice: invoice, source: .message(messageId), inputData: starsInputData) + strongSelf.push(controller) + }) + } else { + strongSelf.present(BotCheckoutController(context: strongSelf.context, invoice: invoice, source: .message(messageId), inputData: inputData, completed: { currencyValue, receiptMessageId in + guard let strongSelf = self else { + return + } + strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .paymentSent(currencyValue: currencyValue, itemTitle: invoice.title), elevatedLayout: false, action: { action in + guard let strongSelf = self, let receiptMessageId = receiptMessageId else { + return false + } + + if case .info = action { + strongSelf.present(BotReceiptController(context: strongSelf.context, messageId: receiptMessageId), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + return true + } return false - } - - if case .info = action { - strongSelf.present(BotReceiptController(context: strongSelf.context, messageId: receiptMessageId), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) - return true - } - return false - }), in: .current) - }), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + }), in: .current) + }), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + } } } } diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index d452f72665..03ea02cfba 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -820,25 +820,45 @@ func openResolvedUrlImpl( if let navigationController = navigationController { let inputData = Promise() inputData.set(BotCheckoutController.InputData.fetch(context: context, source: .slug(slug)) - |> map(Optional.init) - |> `catch` { _ -> Signal in + |> map(Optional.init) + |> `catch` { _ -> Signal in return .single(nil) }) - let checkoutController = BotCheckoutController(context: context, invoice: invoice, source: .slug(slug), inputData: inputData, completed: { currencyValue, receiptMessageId in - /*strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .paymentSent(currencyValue: currencyValue, itemTitle: invoice.title), elevatedLayout: false, action: { action in - guard let strongSelf = self, let receiptMessageId = receiptMessageId else { - return false - } - - if case .info = action { - strongSelf.present(BotReceiptController(context: strongSelf.context, messageId: receiptMessageId), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) - return true - } - return false - }), in: .current)*/ - }) - checkoutController.navigationPresentation = .modal - navigationController.pushViewController(checkoutController) + if invoice.currency == "XTR" { + let statePromise = Promise() + statePromise.set(context.engine.payments.peerStarsState(peerId: context.account.peerId)) + let starsInputData = combineLatest( + inputData.get(), + statePromise.get() + ) + |> map { data, state -> (StarsContext.State, BotPaymentForm, EnginePeer?)? in + if let data, let state { + return (state, data.form, data.botPeer) + } else { + return nil + } + } + let _ = (starsInputData |> take(1) |> deliverOnMainQueue).start(next: { _ in + let controller = context.sharedContext.makeStarsTransferScreen(context: context, invoice: invoice, source: .slug(slug), inputData: starsInputData) + navigationController.pushViewController(controller) + }) + } else { + let checkoutController = BotCheckoutController(context: context, invoice: invoice, source: .slug(slug), inputData: inputData, completed: { currencyValue, receiptMessageId in + /*strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .paymentSent(currencyValue: currencyValue, itemTitle: invoice.title), elevatedLayout: false, action: { action in + guard let strongSelf = self, let receiptMessageId = receiptMessageId else { + return false + } + + if case .info = action { + strongSelf.present(BotReceiptController(context: strongSelf.context, messageId: receiptMessageId), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + return true + } + return false + }), in: .current)*/ + }) + checkoutController.navigationPresentation = .modal + navigationController.pushViewController(checkoutController) + } } } else { present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Chat_ErrorInvoiceNotFound, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 2b38c97041..879bb5e97e 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -64,6 +64,9 @@ import TelegramNotices import BotSettingsScreen import CameraScreen import BirthdayPickerScreen +import StarsTransactionsScreen +import StarsPurchaseScreen +import StarsTransferScreen private final class AccountUserInterfaceInUseContext { let subscribers = Bag<(Bool) -> Void>() @@ -2609,6 +2612,18 @@ public final class SharedAccountContextImpl: SharedAccountContext { public func makeStoryStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peerId: EnginePeer.Id, storyId: Int32, storyItem: EngineStoryItem, fromStory: Bool) -> ViewController { return messageStatsController(context: context, updatedPresentationData: updatedPresentationData, subject: .story(peerId: peerId, id: storyId, item: storyItem, fromStory: fromStory)) } + + public func makeStarsTransactionsScreen(context: AccountContext, starsContext: StarsContext) -> ViewController { + return StarsTransactionsScreen(context: context, starsContext: starsContext) + } + + public func makeStarsPurchaseScreen(context: AccountContext, starsContext: StarsContext, options: [StarsTopUpOption], peerId: EnginePeer.Id?, requiredStars: Int32?) -> ViewController { + return StarsPurchaseScreen(context: context, starsContext: starsContext, options: options, peerId: peerId, requiredStars: requiredStars, modal: true) + } + + public func makeStarsTransferScreen(context: AccountContext, invoice: TelegramMediaInvoice, source: BotPaymentInvoiceSource, inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError>) -> ViewController { + return StarsTransferScreen(context: context, invoice: invoice, source: source, inputData: inputData) + } } private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? { diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 83aa107c7c..75d2429024 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -857,14 +857,28 @@ public final class WebAppController: ViewController, AttachmentContainable { return .single(nil) } |> deliverOnMainQueue).start(next: { [weak self] invoice in - if let strongSelf = self, let invoice = invoice { + if let strongSelf = self, let invoice, let navigationController = strongSelf.controller?.getNavigationController() { let inputData = Promise() inputData.set(BotCheckoutController.InputData.fetch(context: strongSelf.context, source: .slug(slug)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) }) - if let navigationController = strongSelf.controller?.getNavigationController() { + if invoice.currency == "XTR" { + let starsInputData = combineLatest( + inputData.get(), + strongSelf.context.engine.payments.peerStarsState(peerId: strongSelf.context.account.peerId) + ) + |> map { data, state -> (StarsContext.State, BotPaymentForm, EnginePeer?)? in + if let data, let state { + return (state, data.form, data.botPeer) + } else { + return nil + } + } + let controller = strongSelf.context.sharedContext.makeStarsTransferScreen(context: strongSelf.context, invoice: invoice, source: .slug(slug), inputData: starsInputData) + navigationController.pushViewController(controller) + } else { let checkoutController = BotCheckoutController(context: strongSelf.context, invoice: invoice, source: .slug(slug), inputData: inputData, completed: { currencyValue, receiptMessageId in self?.sendInvoiceClosedEvent(slug: slug, result: .paid) }, cancelled: { [weak self] in